virt: Apply formatting rules on plugin code
[collectd.git] / src / virt.c
1 /**
2  * collectd - src/virt.c
3  * Copyright (C) 2006-2008  Red Hat Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the license is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Richard W.M. Jones <rjones@redhat.com>
20  *   Przemyslaw Szczerbik <przemyslawx.szczerbik@intel.com>
21  **/
22
23 #include "collectd.h"
24
25 #include "common.h"
26 #include "plugin.h"
27 #include "utils_complain.h"
28 #include "utils_ignorelist.h"
29
30 #include <libgen.h> /* for basename(3) */
31 #include <libvirt/libvirt.h>
32 #include <libvirt/virterror.h>
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xpath.h>
36 #include <libxml/xpathInternals.h>
37
38 /* Plugin name */
39 #define PLUGIN_NAME "virt"
40
41 #ifdef LIBVIR_CHECK_VERSION
42
43 #if LIBVIR_CHECK_VERSION(0, 9, 2)
44 #define HAVE_DOM_REASON 1
45 #endif
46
47 #if LIBVIR_CHECK_VERSION(0, 9, 5)
48 #define HAVE_BLOCK_STATS_FLAGS 1
49 #define HAVE_DOM_REASON_PAUSED_SHUTTING_DOWN 1
50 #endif
51
52 #if LIBVIR_CHECK_VERSION(0, 9, 10)
53 #define HAVE_DISK_ERR 1
54 #endif
55
56 #if LIBVIR_CHECK_VERSION(0, 9, 11)
57 #define HAVE_CPU_STATS 1
58 #define HAVE_DOM_STATE_PMSUSPENDED 1
59 #define HAVE_DOM_REASON_RUNNING_WAKEUP 1
60 #endif
61
62 /*
63   virConnectListAllDomains() appeared in 0.10.2
64   Note that LIBVIR_CHECK_VERSION appeared a year later, so
65   in some systems which actually have virConnectListAllDomains()
66   we can't detect this.
67  */
68 #ifdef LIBVIR_CHECK_VERSION
69 #if LIBVIR_CHECK_VERSION(0, 10, 2)
70 #define HAVE_LIST_ALL_DOMAINS 1
71 #endif
72 #endif
73
74 #if LIBVIR_CHECK_VERSION(1, 0, 1)
75 #define HAVE_DOM_REASON_PAUSED_SNAPSHOT 1
76 #endif
77
78 #if LIBVIR_CHECK_VERSION(1, 1, 1)
79 #define HAVE_DOM_REASON_PAUSED_CRASHED 1
80 #endif
81
82 #if LIBVIR_CHECK_VERSION(1, 2, 9)
83 #define HAVE_JOB_STATS 1
84 #endif
85
86 #if LIBVIR_CHECK_VERSION(1, 2, 10)
87 #define HAVE_DOM_REASON_CRASHED 1
88 #endif
89
90 #if LIBVIR_CHECK_VERSION(1, 2, 11)
91 #define HAVE_FS_INFO 1
92 #endif
93
94 #if LIBVIR_CHECK_VERSION(1, 2, 15)
95 #define HAVE_DOM_REASON_PAUSED_STARTING_UP 1
96 #endif
97
98 #if LIBVIR_CHECK_VERSION(1, 3, 3)
99 #define HAVE_PERF_STATS 1
100 #define HAVE_DOM_REASON_POSTCOPY 1
101 #endif
102
103 #endif /* LIBVIR_CHECK_VERSION */
104
105 static const char *config_keys[] = {"Connection",
106
107                                     "RefreshInterval",
108
109                                     "Domain",
110                                     "BlockDevice",
111                                     "BlockDeviceFormat",
112                                     "BlockDeviceFormatBasename",
113                                     "InterfaceDevice",
114                                     "IgnoreSelected",
115
116                                     "HostnameFormat",
117                                     "InterfaceFormat",
118
119                                     "PluginInstanceFormat",
120
121                                     "Instances",
122                                     "ExtraStats",
123                                     "PersistentNotification",
124                                     NULL};
125
126 /* PersistentNotification is false by default */
127 static _Bool persistent_notification = 0;
128
129 /* libvirt event loop */
130 static pthread_t event_loop_tid;
131
132 static int domain_event_cb_id;
133
134 const char *domain_states[] = {
135         [VIR_DOMAIN_NOSTATE] = "no state",
136         [VIR_DOMAIN_RUNNING] = "the domain is running",
137         [VIR_DOMAIN_BLOCKED] = "the domain is blocked on resource",
138         [VIR_DOMAIN_PAUSED] = "the domain is paused by user",
139         [VIR_DOMAIN_SHUTDOWN] = "the domain is being shut down",
140         [VIR_DOMAIN_SHUTOFF] = "the domain is shut off",
141         [VIR_DOMAIN_CRASHED] = "the domain is crashed",
142 #ifdef HAVE_DOM_STATE_PMSUSPENDED
143         [VIR_DOMAIN_PMSUSPENDED] =
144             "the domain is suspended by guest power management",
145 #endif
146 };
147
148 static int map_domain_event_to_state(int event) {
149   int ret;
150   switch (event) {
151   case VIR_DOMAIN_EVENT_STARTED:
152     ret = VIR_DOMAIN_RUNNING;
153     break;
154   case VIR_DOMAIN_EVENT_SUSPENDED:
155     ret = VIR_DOMAIN_PAUSED;
156     break;
157   case VIR_DOMAIN_EVENT_RESUMED:
158     ret = VIR_DOMAIN_RUNNING;
159     break;
160   case VIR_DOMAIN_EVENT_STOPPED:
161     ret = VIR_DOMAIN_SHUTOFF;
162     break;
163   case VIR_DOMAIN_EVENT_SHUTDOWN:
164     ret = VIR_DOMAIN_SHUTDOWN;
165     break;
166   case VIR_DOMAIN_EVENT_PMSUSPENDED:
167     ret = VIR_DOMAIN_PMSUSPENDED;
168     break;
169   case VIR_DOMAIN_EVENT_CRASHED:
170     ret = VIR_DOMAIN_CRASHED;
171     break;
172   default:
173     ret = VIR_DOMAIN_NOSTATE;
174   }
175   return ret;
176 }
177
178 static int map_domain_event_detail_to_reason(int event, int detail) {
179   int ret;
180   switch (event) {
181   case VIR_DOMAIN_EVENT_STARTED:
182     switch (detail) {
183     case VIR_DOMAIN_EVENT_STARTED_BOOTED: /* Normal startup from boot */
184       ret = VIR_DOMAIN_RUNNING_BOOTED;
185       break;
186     case VIR_DOMAIN_EVENT_STARTED_MIGRATED: /* Incoming migration from another
187                                                host */
188       ret = VIR_DOMAIN_RUNNING_MIGRATED;
189       break;
190     case VIR_DOMAIN_EVENT_STARTED_RESTORED: /* Restored from a state file */
191       ret = VIR_DOMAIN_RUNNING_RESTORED;
192       break;
193     case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT: /* Restored from snapshot */
194       ret = VIR_DOMAIN_RUNNING_FROM_SNAPSHOT;
195       break;
196     case VIR_DOMAIN_EVENT_STARTED_WAKEUP: /* Started due to wakeup event */
197       ret = VIR_DOMAIN_RUNNING_WAKEUP;
198       break;
199     default:
200       ret = VIR_DOMAIN_RUNNING_UNKNOWN;
201     }
202     break;
203   case VIR_DOMAIN_EVENT_SUSPENDED:
204     switch (detail) {
205     case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED: /* Normal suspend due to admin
206                                                pause */
207       ret = VIR_DOMAIN_PAUSED_USER;
208       break;
209     case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED: /* Suspended for offline
210                                                  migration */
211       ret = VIR_DOMAIN_PAUSED_MIGRATION;
212       break;
213     case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR: /* Suspended due to a disk I/O
214                                                 error */
215       ret = VIR_DOMAIN_PAUSED_IOERROR;
216       break;
217     case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG: /* Suspended due to a watchdog
218                                                  firing */
219       ret = VIR_DOMAIN_PAUSED_WATCHDOG;
220       break;
221     case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED: /* Restored from paused state
222                                                  file */
223       ret = VIR_DOMAIN_PAUSED_UNKNOWN;
224       break;
225     case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT: /* Restored from paused
226                                                       snapshot */
227       ret = VIR_DOMAIN_PAUSED_FROM_SNAPSHOT;
228       break;
229     case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR: /* Suspended after failure during
230                                                   libvirt API call */
231       ret = VIR_DOMAIN_PAUSED_UNKNOWN;
232       break;
233     case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY: /* Suspended for post-copy
234                                                  migration */
235       ret = VIR_DOMAIN_PAUSED_POSTCOPY;
236       break;
237     case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED: /* Suspended after failed
238                                                         post-copy */
239       ret = VIR_DOMAIN_PAUSED_POSTCOPY_FAILED;
240       break;
241     default:
242       ret = VIR_DOMAIN_PAUSED_UNKNOWN;
243     }
244     break;
245   case VIR_DOMAIN_EVENT_RESUMED:
246     switch (detail) {
247     case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED: /* Normal resume due to admin
248                                                unpause */
249       ret = VIR_DOMAIN_RUNNING_UNPAUSED;
250       break;
251     case VIR_DOMAIN_EVENT_RESUMED_MIGRATED: /* Resumed for completion of
252                                                migration */
253       ret = VIR_DOMAIN_RUNNING_MIGRATED;
254       break;
255     case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: /* Resumed from snapshot */
256       ret = VIR_DOMAIN_RUNNING_FROM_SNAPSHOT;
257       break;
258     case VIR_DOMAIN_EVENT_RESUMED_POSTCOPY: /* Resumed, but migration is still
259                                                running in post-copy mode */
260       ret = VIR_DOMAIN_RUNNING_POSTCOPY;
261       break;
262     default:
263       ret = VIR_DOMAIN_RUNNING_UNKNOWN;
264     }
265     break;
266   case VIR_DOMAIN_EVENT_STOPPED:
267     switch (detail) {
268     case VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN: /* Normal shutdown */
269       ret = VIR_DOMAIN_SHUTOFF_SHUTDOWN;
270       break;
271     case VIR_DOMAIN_EVENT_STOPPED_DESTROYED: /* Forced poweroff from host */
272       ret = VIR_DOMAIN_SHUTOFF_DESTROYED;
273       break;
274     case VIR_DOMAIN_EVENT_STOPPED_CRASHED: /* Guest crashed */
275       ret = VIR_DOMAIN_SHUTOFF_CRASHED;
276       break;
277     case VIR_DOMAIN_EVENT_STOPPED_MIGRATED: /* Migrated off to another host */
278       ret = VIR_DOMAIN_SHUTOFF_MIGRATED;
279       break;
280     case VIR_DOMAIN_EVENT_STOPPED_SAVED: /* Saved to a state file */
281       ret = VIR_DOMAIN_SHUTOFF_SAVED;
282       break;
283     case VIR_DOMAIN_EVENT_STOPPED_FAILED: /* Host emulator/mgmt failed */
284       ret = VIR_DOMAIN_SHUTOFF_FAILED;
285       break;
286     case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT: /* Offline snapshot loaded */
287       ret = VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT;
288       break;
289     default:
290       ret = VIR_DOMAIN_SHUTOFF_UNKNOWN;
291     }
292     break;
293   case VIR_DOMAIN_EVENT_SHUTDOWN:
294     switch (detail) {
295     case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: /* Guest finished shutdown
296                                                 sequence */
297       ret = VIR_DOMAIN_SHUTDOWN_USER;
298       break;
299     default:
300       ret = VIR_DOMAIN_SHUTDOWN_UNKNOWN;
301     }
302     break;
303   case VIR_DOMAIN_EVENT_PMSUSPENDED:
304     switch (detail) {
305     case VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY: /* Guest was PM suspended to
306                                                  memory */
307       ret = VIR_DOMAIN_PMSUSPENDED_UNKNOWN;
308       break;
309     case VIR_DOMAIN_EVENT_PMSUSPENDED_DISK: /* Guest was PM suspended to disk */
310       ret = VIR_DOMAIN_PMSUSPENDED_DISK_UNKNOWN;
311       break;
312     default:
313       ret = VIR_DOMAIN_PMSUSPENDED_UNKNOWN;
314     }
315     break;
316   case VIR_DOMAIN_EVENT_CRASHED:
317     switch (detail) {
318     case VIR_DOMAIN_EVENT_CRASHED_PANICKED: /* Guest was panicked */
319       ret = VIR_DOMAIN_CRASHED_PANICKED;
320       break;
321     default:
322       ret = VIR_DOMAIN_CRASHED_UNKNOWN;
323     }
324     break;
325   default:
326     ret = VIR_DOMAIN_NOSTATE_UNKNOWN;
327   }
328   return ret;
329 }
330
331 #ifdef HAVE_DOM_REASON
332 #define DOMAIN_STATE_REASON_MAX_SIZE 20
333 const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
334         [VIR_DOMAIN_NOSTATE][VIR_DOMAIN_NOSTATE_UNKNOWN] =
335             "the reason is unknown",
336
337         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_UNKNOWN] =
338             "the reason is unknown",
339         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_BOOTED] =
340             "normal startup from boot",
341         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_MIGRATED] =
342             "migrated from another host",
343         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_RESTORED] =
344             "restored from a state file",
345         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_FROM_SNAPSHOT] =
346             "restored from snapshot",
347         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_UNPAUSED] =
348             "returned from paused state",
349         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_MIGRATION_CANCELED] =
350             "returned from migration",
351         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_SAVE_CANCELED] =
352             "returned from failed save process",
353 #ifdef HAVE_DOM_REASON_RUNNING_WAKEUP
354         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_WAKEUP] =
355             "returned from pmsuspended due to wakeup event",
356 #endif
357 #ifdef HAVE_DOM_REASON_CRASHED
358         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_CRASHED] =
359             "resumed from crashed",
360 #endif
361 #ifdef HAVE_DOM_REASON_POSTCOPY
362         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_POSTCOPY] =
363             "running in post-copy migration mode",
364 #endif
365
366         [VIR_DOMAIN_BLOCKED][VIR_DOMAIN_BLOCKED_UNKNOWN] =
367             "the reason is unknown",
368
369         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_UNKNOWN] =
370             "the reason is unknown",
371         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_USER] = "paused on user request",
372         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_MIGRATION] =
373             "paused for offline migration",
374         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SAVE] = "paused for save",
375         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_DUMP] =
376             "paused for offline core dump",
377         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_IOERROR] =
378             "paused due to a disk I/O error",
379         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_WATCHDOG] =
380             "paused due to a watchdog event",
381         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_FROM_SNAPSHOT] =
382             "paused after restoring from snapshot",
383 #ifdef HAVE_DOM_REASON_PAUSED_SHUTTING_DOWN
384         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SHUTTING_DOWN] =
385             "paused during shutdown process",
386 #endif
387 #ifdef HAVE_DOM_REASON_PAUSED_SNAPSHOT
388         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SNAPSHOT] =
389             "paused while creating a snapshot",
390 #endif
391 #ifdef HAVE_DOM_REASON_PAUSED_CRASHED
392         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_CRASHED] =
393             "paused due to a guest crash",
394 #endif
395 #ifdef HAVE_DOM_REASON_PAUSED_STARTING_UP
396         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_STARTING_UP] =
397             "the domain is being started",
398 #endif
399 #ifdef HAVE_DOM_REASON_POSTCOPY
400         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_POSTCOPY] =
401             "paused for post-copy migration",
402         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_POSTCOPY_FAILED] =
403             "paused after failed post-copy",
404 #endif
405
406         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_UNKNOWN] =
407             "the reason is unknown",
408         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_USER] =
409             "shutting down on user request",
410
411         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_UNKNOWN] =
412             "the reason is unknown",
413         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_SHUTDOWN] = "normal shutdown",
414         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_DESTROYED] = "forced poweroff",
415         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_CRASHED] = "domain crashed",
416         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_MIGRATED] =
417             "migrated to another host",
418         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_SAVED] = "saved to a file",
419         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FAILED] =
420             "domain failed to start",
421         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT] =
422             "restored from a snapshot which was taken while domain was shutoff",
423
424         [VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_UNKNOWN] =
425             "the reason is unknown",
426 #ifdef VIR_DOMAIN_CRASHED_PANICKED
427         [VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_PANICKED] = "domain panicked",
428 #endif
429
430 #ifdef HAVE_DOM_STATE_PMSUSPENDED
431         [VIR_DOMAIN_PMSUSPENDED][VIR_DOMAIN_PMSUSPENDED_UNKNOWN] =
432             "the reason is unknown",
433 #endif
434 };
435 #endif /* HAVE_DOM_REASON */
436
437 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
438 #define NANOSEC_IN_SEC 1e9
439
440 #define GET_STATS(_f, _name, ...)                                              \
441   do {                                                                         \
442     status = _f(__VA_ARGS__);                                                  \
443     if (status != 0)                                                           \
444       ERROR(PLUGIN_NAME ": Failed to get " _name);                             \
445   } while (0)
446
447 /* Connection. */
448 static virConnectPtr conn = 0;
449 static char *conn_string = NULL;
450 static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC;
451
452 /* Node information required for %CPU */
453 static virNodeInfo nodeinfo;
454
455 /* Seconds between list refreshes, 0 disables completely. */
456 static int interval = 60;
457
458 /* List of domains, if specified. */
459 static ignorelist_t *il_domains = NULL;
460 /* List of block devices, if specified. */
461 static ignorelist_t *il_block_devices = NULL;
462 /* List of network interface devices, if specified. */
463 static ignorelist_t *il_interface_devices = NULL;
464
465 static int ignore_device_match(ignorelist_t *, const char *domname,
466                                const char *devpath);
467
468 /* Actual list of block devices found on last refresh. */
469 struct block_device {
470   virDomainPtr dom; /* domain */
471   char *path;       /* name of block device */
472 };
473
474 /* Actual list of network interfaces found on last refresh. */
475 struct interface_device {
476   virDomainPtr dom; /* domain */
477   char *path;       /* name of interface device */
478   char *address;    /* mac address of interface device */
479   char *number;     /* interface device number */
480 };
481
482 typedef struct domain_s {
483   virDomainPtr ptr;
484   virDomainInfo info;
485   _Bool active;
486 } domain_t;
487
488 struct lv_read_state {
489   /* Actual list of domains found on last refresh. */
490   domain_t *domains;
491   int nr_domains;
492
493   struct block_device *block_devices;
494   int nr_block_devices;
495
496   struct interface_device *interface_devices;
497   int nr_interface_devices;
498 };
499
500 static void free_domains(struct lv_read_state *state);
501 static int add_domain(struct lv_read_state *state, virDomainPtr dom,
502                       _Bool active);
503
504 static void free_block_devices(struct lv_read_state *state);
505 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
506                             const char *path);
507
508 static void free_interface_devices(struct lv_read_state *state);
509 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
510                                 const char *path, const char *address,
511                                 unsigned int number);
512
513 #define METADATA_VM_PARTITION_URI "http://ovirt.org/ovirtmap/tag/1.0"
514 #define METADATA_VM_PARTITION_ELEMENT "tag"
515 #define METADATA_VM_PARTITION_PREFIX "ovirtmap"
516
517 #define BUFFER_MAX_LEN 256
518 #define PARTITION_TAG_MAX_LEN 32
519
520 struct lv_read_instance {
521   struct lv_read_state read_state;
522   char tag[PARTITION_TAG_MAX_LEN];
523   size_t id;
524 };
525
526 struct lv_user_data {
527   struct lv_read_instance inst;
528   user_data_t ud;
529 };
530
531 #define NR_INSTANCES_DEFAULT 1
532 #define NR_INSTANCES_MAX 128
533 static int nr_instances = NR_INSTANCES_DEFAULT;
534 static struct lv_user_data lv_read_user_data[NR_INSTANCES_MAX];
535
536 /* HostnameFormat. */
537 #define HF_MAX_FIELDS 3
538
539 enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid };
540
541 static enum hf_field hostname_format[HF_MAX_FIELDS] = {hf_name};
542
543 /* PluginInstanceFormat */
544 #define PLGINST_MAX_FIELDS 2
545
546 enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid };
547
548 static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = {
549     plginst_none};
550
551 /* BlockDeviceFormat */
552 enum bd_field { target, source };
553
554 /* InterfaceFormat. */
555 enum if_field { if_address, if_name, if_number };
556
557 /* ExtraStats */
558 #define EX_STATS_MAX_FIELDS 15
559 enum ex_stats {
560   ex_stats_none = 0,
561   ex_stats_disk = 1 << 0,
562   ex_stats_pcpu = 1 << 1,
563   ex_stats_cpu_util = 1 << 2,
564   ex_stats_domain_state = 1 << 3,
565 #ifdef HAVE_PERF_STATS
566   ex_stats_perf = 1 << 4,
567 #endif
568   ex_stats_vcpupin = 1 << 5,
569 #ifdef HAVE_DISK_ERR
570   ex_stats_disk_err = 1 << 6,
571 #endif
572 #ifdef HAVE_FS_INFO
573   ex_stats_fs_info = 1 << 7,
574 #endif
575 #ifdef HAVE_JOB_STATS
576   ex_stats_job_stats_completed = 1 << 8,
577   ex_stats_job_stats_background = 1 << 9,
578 #endif
579 };
580
581 static unsigned int extra_stats = ex_stats_none;
582
583 struct ex_stats_item {
584   const char *name;
585   enum ex_stats flag;
586 };
587 static const struct ex_stats_item ex_stats_table[] = {
588     {"disk", ex_stats_disk},
589     {"pcpu", ex_stats_pcpu},
590     {"cpu_util", ex_stats_cpu_util},
591     {"domain_state", ex_stats_domain_state},
592 #ifdef HAVE_PERF_STATS
593     {"perf", ex_stats_perf},
594 #endif
595     {"vcpupin", ex_stats_vcpupin},
596 #ifdef HAVE_DISK_ERR
597     {"disk_err", ex_stats_disk_err},
598 #endif
599 #ifdef HAVE_FS_INFO
600     {"fs_info", ex_stats_fs_info},
601 #endif
602 #ifdef HAVE_JOB_STATS
603     {"job_stats_completed", ex_stats_job_stats_completed},
604     {"job_stats_background", ex_stats_job_stats_background},
605 #endif
606     {NULL, ex_stats_none},
607 };
608
609 /* BlockDeviceFormatBasename */
610 _Bool blockdevice_format_basename = 0;
611 static enum bd_field blockdevice_format = target;
612 static enum if_field interface_format = if_name;
613
614 /* Time that we last refreshed. */
615 static time_t last_refresh = (time_t)0;
616
617 static int refresh_lists(struct lv_read_instance *inst);
618
619 struct lv_info {
620   virDomainInfo di;
621   unsigned long long total_user_cpu_time;
622   unsigned long long total_syst_cpu_time;
623 };
624
625 struct lv_block_info {
626   virDomainBlockStatsStruct bi;
627
628   long long rd_total_times;
629   long long wr_total_times;
630
631   long long fl_req;
632   long long fl_total_times;
633 };
634
635 static void init_block_info(struct lv_block_info *binfo) {
636   if (binfo == NULL)
637     return;
638
639   binfo->bi.rd_req = -1;
640   binfo->bi.wr_req = -1;
641   binfo->bi.rd_bytes = -1;
642   binfo->bi.wr_bytes = -1;
643
644   binfo->rd_total_times = -1;
645   binfo->wr_total_times = -1;
646   binfo->fl_req = -1;
647   binfo->fl_total_times = -1;
648 }
649
650 #ifdef HAVE_BLOCK_STATS_FLAGS
651
652 #define GET_BLOCK_INFO_VALUE(NAME, FIELD)                                      \
653   if (!strcmp(param[i].field, NAME)) {                                         \
654     binfo->FIELD = param[i].value.l;                                           \
655     continue;                                                                  \
656   }
657
658 static int get_block_info(struct lv_block_info *binfo,
659                           virTypedParameterPtr param, int nparams) {
660   if (binfo == NULL || param == NULL)
661     return -1;
662
663   for (int i = 0; i < nparams; ++i) {
664     /* ignore type. Everything must be LLONG anyway. */
665     GET_BLOCK_INFO_VALUE("rd_operations", bi.rd_req);
666     GET_BLOCK_INFO_VALUE("wr_operations", bi.wr_req);
667     GET_BLOCK_INFO_VALUE("rd_bytes", bi.rd_bytes);
668     GET_BLOCK_INFO_VALUE("wr_bytes", bi.wr_bytes);
669     GET_BLOCK_INFO_VALUE("rd_total_times", rd_total_times);
670     GET_BLOCK_INFO_VALUE("wr_total_times", wr_total_times);
671     GET_BLOCK_INFO_VALUE("flush_operations", fl_req);
672     GET_BLOCK_INFO_VALUE("flush_total_times", fl_total_times);
673   }
674
675   return 0;
676 }
677
678 #undef GET_BLOCK_INFO_VALUE
679
680 #endif /* HAVE_BLOCK_STATS_FLAGS */
681
682 /* ERROR(...) macro for virterrors. */
683 #define VIRT_ERROR(conn, s)                                                    \
684   do {                                                                         \
685     virErrorPtr err;                                                           \
686     err = (conn) ? virConnGetLastError((conn)) : virGetLastError();            \
687     if (err)                                                                   \
688       ERROR("%s: %s", (s), err->message);                                      \
689   } while (0)
690
691 static void init_lv_info(struct lv_info *info) {
692   if (info != NULL)
693     memset(info, 0, sizeof(*info));
694 }
695
696 static int lv_domain_info(virDomainPtr dom, struct lv_info *info) {
697 #ifdef HAVE_CPU_STATS
698   virTypedParameterPtr param = NULL;
699   int nparams = 0;
700 #endif /* HAVE_CPU_STATS */
701   int ret = virDomainGetInfo(dom, &(info->di));
702   if (ret != 0) {
703     return ret;
704   }
705
706 #ifdef HAVE_CPU_STATS
707   nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0);
708   if (nparams < 0) {
709     VIRT_ERROR(conn, "getting the CPU params count");
710     return -1;
711   }
712
713   param = calloc(nparams, sizeof(virTypedParameter));
714   if (param == NULL) {
715     ERROR("virt plugin: alloc(%i) for cpu parameters failed.", nparams);
716     return -1;
717   }
718
719   ret = virDomainGetCPUStats(dom, param, nparams, -1, 1, 0); // total stats.
720   if (ret < 0) {
721     virTypedParamsClear(param, nparams);
722     sfree(param);
723     VIRT_ERROR(conn, "getting the disk params values");
724     return -1;
725   }
726
727   for (int i = 0; i < nparams; ++i) {
728     if (!strcmp(param[i].field, "user_time"))
729       info->total_user_cpu_time = param[i].value.ul;
730     else if (!strcmp(param[i].field, "system_time"))
731       info->total_syst_cpu_time = param[i].value.ul;
732   }
733
734   virTypedParamsClear(param, nparams);
735   sfree(param);
736 #endif /* HAVE_CPU_STATS */
737
738   return 0;
739 }
740
741 static void init_value_list(value_list_t *vl, virDomainPtr dom) {
742   int n;
743   const char *name;
744   char uuid[VIR_UUID_STRING_BUFLEN];
745
746   sstrncpy(vl->plugin, PLUGIN_NAME, sizeof(vl->plugin));
747
748   vl->host[0] = '\0';
749
750   /* Construct the hostname field according to HostnameFormat. */
751   for (int i = 0; i < HF_MAX_FIELDS; ++i) {
752     if (hostname_format[i] == hf_none)
753       continue;
754
755     n = DATA_MAX_NAME_LEN - strlen(vl->host) - 2;
756
757     if (i > 0 && n >= 1) {
758       strncat(vl->host, ":", 1);
759       n--;
760     }
761
762     switch (hostname_format[i]) {
763     case hf_none:
764       break;
765     case hf_hostname:
766       strncat(vl->host, hostname_g, n);
767       break;
768     case hf_name:
769       name = virDomainGetName(dom);
770       if (name)
771         strncat(vl->host, name, n);
772       break;
773     case hf_uuid:
774       if (virDomainGetUUIDString(dom, uuid) == 0)
775         strncat(vl->host, uuid, n);
776       break;
777     }
778   }
779
780   vl->host[sizeof(vl->host) - 1] = '\0';
781
782   /* Construct the plugin instance field according to PluginInstanceFormat. */
783   for (int i = 0; i < PLGINST_MAX_FIELDS; ++i) {
784     if (plugin_instance_format[i] == plginst_none)
785       continue;
786
787     n = sizeof(vl->plugin_instance) - strlen(vl->plugin_instance) - 2;
788
789     if (i > 0 && n >= 1) {
790       strncat(vl->plugin_instance, ":", 1);
791       n--;
792     }
793
794     switch (plugin_instance_format[i]) {
795     case plginst_none:
796       break;
797     case plginst_name:
798       name = virDomainGetName(dom);
799       if (name)
800         strncat(vl->plugin_instance, name, n);
801       break;
802     case plginst_uuid:
803       if (virDomainGetUUIDString(dom, uuid) == 0)
804         strncat(vl->plugin_instance, uuid, n);
805       break;
806     }
807   }
808
809   vl->plugin_instance[sizeof(vl->plugin_instance) - 1] = '\0';
810
811 } /* void init_value_list */
812
813 static int init_notif(notification_t *notif, const virDomainPtr domain,
814                       int severity, const char *msg, const char *type,
815                       const char *type_instance) {
816   value_list_t vl = VALUE_LIST_INIT;
817
818   if (!notif) {
819     ERROR(PLUGIN_NAME ": init_notif: NULL pointer");
820     return -1;
821   }
822
823   init_value_list(&vl, domain);
824   notification_init(notif, severity, msg, vl.host, vl.plugin,
825                     vl.plugin_instance, type, type_instance);
826   notif->time = cdtime();
827   return 0;
828 }
829
830 static void submit_notif(const virDomainPtr domain, int severity,
831                          const char *msg, const char *type,
832                          const char *type_instance) {
833   notification_t notif;
834
835   init_notif(&notif, domain, severity, msg, type, type_instance);
836   plugin_dispatch_notification(&notif);
837   if (notif.meta)
838     plugin_notification_meta_free(notif.meta);
839 }
840
841 static void submit(virDomainPtr dom, char const *type,
842                    char const *type_instance, value_t *values,
843                    size_t values_len) {
844   value_list_t vl = VALUE_LIST_INIT;
845   init_value_list(&vl, dom);
846
847   vl.values = values;
848   vl.values_len = values_len;
849
850   sstrncpy(vl.type, type, sizeof(vl.type));
851   if (type_instance != NULL)
852     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
853
854   plugin_dispatch_values(&vl);
855 }
856
857 static void memory_submit(virDomainPtr dom, gauge_t value) {
858   submit(dom, "memory", "total", &(value_t){.gauge = value}, 1);
859 }
860
861 static void memory_stats_submit(gauge_t value, virDomainPtr dom,
862                                 int tag_index) {
863   static const char *tags[] = {"swap_in",        "swap_out", "major_fault",
864                                "minor_fault",    "unused",   "available",
865                                "actual_balloon", "rss",      "usable",
866                                "last_update"};
867
868   if ((tag_index < 0) || (tag_index >= (int)STATIC_ARRAY_SIZE(tags))) {
869     ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
870     return;
871   }
872
873   submit(dom, "memory", tags[tag_index], &(value_t){.gauge = value}, 1);
874 }
875
876 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
877                            virDomainPtr dom, const char *devname) {
878   value_t values[] = {
879       {.derive = v0}, {.derive = v1},
880   };
881
882   submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
883 } /* void submit_derive2 */
884
885 static void pcpu_submit(virDomainPtr dom, struct lv_info *info) {
886 #ifdef HAVE_CPU_STATS
887   if (extra_stats & ex_stats_pcpu)
888     submit_derive2("ps_cputime", info->total_user_cpu_time,
889                    info->total_syst_cpu_time, dom, NULL);
890 #endif /* HAVE_CPU_STATS */
891 }
892
893 static double cpu_ns_to_percent(unsigned int node_cpus,
894                                 unsigned long long cpu_time_old,
895                                 unsigned long long cpu_time_new) {
896   double percent = 0.0;
897   unsigned long long cpu_time_diff = 0;
898   double time_diff_sec = CDTIME_T_TO_DOUBLE(plugin_get_interval());
899
900   if (node_cpus != 0 && time_diff_sec != 0 && cpu_time_old != 0) {
901     cpu_time_diff = cpu_time_new - cpu_time_old;
902     percent = ((double)(100 * cpu_time_diff)) /
903               (time_diff_sec * node_cpus * NANOSEC_IN_SEC);
904   }
905
906   DEBUG(PLUGIN_NAME ": node_cpus=%u cpu_time_old=%" PRIu64
907                     " cpu_time_new=%" PRIu64 "cpu_time_diff=%" PRIu64
908                     " time_diff_sec=%f percent=%f",
909         node_cpus, (uint64_t)cpu_time_old, (uint64_t)cpu_time_new,
910         (uint64_t)cpu_time_diff, time_diff_sec, percent);
911
912   return percent;
913 }
914
915 static void cpu_submit(const domain_t *dom, unsigned long long cpuTime_new) {
916
917   if (!dom)
918     return;
919
920   if (extra_stats & ex_stats_cpu_util) {
921     /* Computing %CPU requires 2 samples of cpuTime */
922     if (dom->info.cpuTime != 0 && cpuTime_new != 0) {
923
924       submit(dom->ptr, "percent", "virt_cpu_total",
925              &(value_t){.gauge = cpu_ns_to_percent(
926                             nodeinfo.cpus, dom->info.cpuTime, cpuTime_new)},
927              1);
928     }
929   }
930
931   submit(dom->ptr, "virt_cpu_total", NULL, &(value_t){.derive = cpuTime_new},
932          1);
933 }
934
935 static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
936                         const char *type) {
937   char type_instance[DATA_MAX_NAME_LEN];
938
939   snprintf(type_instance, sizeof(type_instance), "%d", vcpu_nr);
940   submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
941 }
942
943 static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
944                         const char *dev) {
945   char *dev_copy = strdup(dev);
946   const char *type_instance = dev_copy;
947
948   if (!dev_copy)
949     return;
950
951   if (blockdevice_format_basename && blockdevice_format == source)
952     type_instance = basename(dev_copy);
953
954   if (!type_instance) {
955     sfree(dev_copy);
956     return;
957   }
958
959   char flush_type_instance[DATA_MAX_NAME_LEN];
960   snprintf(flush_type_instance, sizeof(flush_type_instance), "flush-%s",
961            type_instance);
962
963   if ((binfo->bi.rd_req != -1) && (binfo->bi.wr_req != -1))
964     submit_derive2("disk_ops", (derive_t)binfo->bi.rd_req,
965                    (derive_t)binfo->bi.wr_req, dom, type_instance);
966
967   if ((binfo->bi.rd_bytes != -1) && (binfo->bi.wr_bytes != -1))
968     submit_derive2("disk_octets", (derive_t)binfo->bi.rd_bytes,
969                    (derive_t)binfo->bi.wr_bytes, dom, type_instance);
970
971   if (extra_stats & ex_stats_disk) {
972     if ((binfo->rd_total_times != -1) && (binfo->wr_total_times != -1))
973       submit_derive2("disk_time", (derive_t)binfo->rd_total_times,
974                      (derive_t)binfo->wr_total_times, dom, type_instance);
975
976     if (binfo->fl_req != -1)
977       submit(dom, "total_requests", flush_type_instance,
978              &(value_t){.derive = (derive_t)binfo->fl_req}, 1);
979     if (binfo->fl_total_times != -1) {
980       derive_t value = binfo->fl_total_times / 1000; // ns -> ms
981       submit(dom, "total_time_in_ms", flush_type_instance,
982              &(value_t){.derive = value}, 1);
983     }
984   }
985
986   sfree(dev_copy);
987 }
988
989 static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
990   unsigned int ex_stats_flags = ex_stats_none;
991   for (int i = 0; i < numexstats; i++) {
992     for (int j = 0; ex_stats_table[j].name != NULL; j++) {
993       if (strcasecmp(exstats[i], ex_stats_table[j].name) == 0) {
994         DEBUG(PLUGIN_NAME " plugin: enabling extra stats for '%s'",
995               ex_stats_table[j].name);
996         ex_stats_flags |= ex_stats_table[j].flag;
997         break;
998       }
999
1000       if (ex_stats_table[j + 1].name == NULL) {
1001         ERROR(PLUGIN_NAME ": Unmatched ExtraStats option: %s", exstats[i]);
1002       }
1003     }
1004   }
1005   return ex_stats_flags;
1006 }
1007
1008 static void domain_state_submit_notif(virDomainPtr dom, int state, int reason) {
1009   if ((state < 0) || (state >= STATIC_ARRAY_SIZE(domain_states))) {
1010     ERROR(PLUGIN_NAME ": Array index out of bounds: state=%d", state);
1011     return;
1012   }
1013
1014   char msg[DATA_MAX_NAME_LEN];
1015   const char *state_str = domain_states[state];
1016 #ifdef HAVE_DOM_REASON
1017   if ((reason < 0) || (reason >= STATIC_ARRAY_SIZE(domain_reasons[0]))) {
1018     ERROR(PLUGIN_NAME ": Array index out of bounds: reason=%d", reason);
1019     return;
1020   }
1021
1022   const char *reason_str = domain_reasons[state][reason];
1023   /* Array size for domain reasons is fixed, but different domain states can
1024    * have different number of reasons. We need to check if reason was
1025    * successfully parsed */
1026   if (!reason_str) {
1027     ERROR(PLUGIN_NAME ": Invalid reason (%d) for domain state: %s", reason,
1028           state_str);
1029     return;
1030   }
1031 #else
1032   const char *reason_str = "N/A";
1033 #endif
1034
1035   snprintf(msg, sizeof(msg), "Domain state: %s. Reason: %s", state_str,
1036            reason_str);
1037
1038   int severity;
1039   switch (state) {
1040   case VIR_DOMAIN_NOSTATE:
1041   case VIR_DOMAIN_RUNNING:
1042   case VIR_DOMAIN_SHUTDOWN:
1043   case VIR_DOMAIN_SHUTOFF:
1044     severity = NOTIF_OKAY;
1045     break;
1046   case VIR_DOMAIN_BLOCKED:
1047   case VIR_DOMAIN_PAUSED:
1048 #ifdef DOM_STATE_PMSUSPENDED
1049   case VIR_DOMAIN_PMSUSPENDED:
1050 #endif
1051     severity = NOTIF_WARNING;
1052     break;
1053   case VIR_DOMAIN_CRASHED:
1054     severity = NOTIF_FAILURE;
1055     break;
1056   default:
1057     ERROR(PLUGIN_NAME ": Unrecognized domain state (%d)", state);
1058     return;
1059   }
1060   submit_notif(dom, severity, msg, "domain_state", NULL);
1061 }
1062
1063 static int lv_config(const char *key, const char *value) {
1064   if (virInitialize() != 0)
1065     return 1;
1066
1067   if (il_domains == NULL)
1068     il_domains = ignorelist_create(1);
1069   if (il_block_devices == NULL)
1070     il_block_devices = ignorelist_create(1);
1071   if (il_interface_devices == NULL)
1072     il_interface_devices = ignorelist_create(1);
1073
1074   if (strcasecmp(key, "Connection") == 0) {
1075     char *tmp = strdup(value);
1076     if (tmp == NULL) {
1077       ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
1078       return 1;
1079     }
1080     sfree(conn_string);
1081     conn_string = tmp;
1082     return 0;
1083   }
1084
1085   if (strcasecmp(key, "RefreshInterval") == 0) {
1086     char *eptr = NULL;
1087     interval = strtol(value, &eptr, 10);
1088     if (eptr == NULL || *eptr != '\0')
1089       return 1;
1090     return 0;
1091   }
1092
1093   if (strcasecmp(key, "Domain") == 0) {
1094     if (ignorelist_add(il_domains, value))
1095       return 1;
1096     return 0;
1097   }
1098   if (strcasecmp(key, "BlockDevice") == 0) {
1099     if (ignorelist_add(il_block_devices, value))
1100       return 1;
1101     return 0;
1102   }
1103
1104   if (strcasecmp(key, "BlockDeviceFormat") == 0) {
1105     if (strcasecmp(value, "target") == 0)
1106       blockdevice_format = target;
1107     else if (strcasecmp(value, "source") == 0)
1108       blockdevice_format = source;
1109     else {
1110       ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
1111       return -1;
1112     }
1113     return 0;
1114   }
1115   if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
1116     blockdevice_format_basename = IS_TRUE(value);
1117     return 0;
1118   }
1119   if (strcasecmp(key, "InterfaceDevice") == 0) {
1120     if (ignorelist_add(il_interface_devices, value))
1121       return 1;
1122     return 0;
1123   }
1124
1125   if (strcasecmp(key, "IgnoreSelected") == 0) {
1126     if (IS_TRUE(value)) {
1127       ignorelist_set_invert(il_domains, 0);
1128       ignorelist_set_invert(il_block_devices, 0);
1129       ignorelist_set_invert(il_interface_devices, 0);
1130     } else {
1131       ignorelist_set_invert(il_domains, 1);
1132       ignorelist_set_invert(il_block_devices, 1);
1133       ignorelist_set_invert(il_interface_devices, 1);
1134     }
1135     return 0;
1136   }
1137
1138   if (strcasecmp(key, "HostnameFormat") == 0) {
1139     char *value_copy;
1140     char *fields[HF_MAX_FIELDS];
1141     int n;
1142
1143     value_copy = strdup(value);
1144     if (value_copy == NULL) {
1145       ERROR(PLUGIN_NAME " plugin: strdup failed.");
1146       return -1;
1147     }
1148
1149     n = strsplit(value_copy, fields, HF_MAX_FIELDS);
1150     if (n < 1) {
1151       sfree(value_copy);
1152       ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
1153       return -1;
1154     }
1155
1156     for (int i = 0; i < n; ++i) {
1157       if (strcasecmp(fields[i], "hostname") == 0)
1158         hostname_format[i] = hf_hostname;
1159       else if (strcasecmp(fields[i], "name") == 0)
1160         hostname_format[i] = hf_name;
1161       else if (strcasecmp(fields[i], "uuid") == 0)
1162         hostname_format[i] = hf_uuid;
1163       else {
1164         ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
1165               fields[i]);
1166         sfree(value_copy);
1167         return -1;
1168       }
1169     }
1170     sfree(value_copy);
1171
1172     for (int i = n; i < HF_MAX_FIELDS; ++i)
1173       hostname_format[i] = hf_none;
1174
1175     return 0;
1176   }
1177
1178   if (strcasecmp(key, "PluginInstanceFormat") == 0) {
1179     char *value_copy;
1180     char *fields[PLGINST_MAX_FIELDS];
1181     int n;
1182
1183     value_copy = strdup(value);
1184     if (value_copy == NULL) {
1185       ERROR(PLUGIN_NAME " plugin: strdup failed.");
1186       return -1;
1187     }
1188
1189     n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
1190     if (n < 1) {
1191       sfree(value_copy);
1192       ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
1193       return -1;
1194     }
1195
1196     for (int i = 0; i < n; ++i) {
1197       if (strcasecmp(fields[i], "none") == 0) {
1198         plugin_instance_format[i] = plginst_none;
1199         break;
1200       } else if (strcasecmp(fields[i], "name") == 0)
1201         plugin_instance_format[i] = plginst_name;
1202       else if (strcasecmp(fields[i], "uuid") == 0)
1203         plugin_instance_format[i] = plginst_uuid;
1204       else {
1205         ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
1206               fields[i]);
1207         sfree(value_copy);
1208         return -1;
1209       }
1210     }
1211     sfree(value_copy);
1212
1213     for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
1214       plugin_instance_format[i] = plginst_none;
1215
1216     return 0;
1217   }
1218
1219   if (strcasecmp(key, "InterfaceFormat") == 0) {
1220     if (strcasecmp(value, "name") == 0)
1221       interface_format = if_name;
1222     else if (strcasecmp(value, "address") == 0)
1223       interface_format = if_address;
1224     else if (strcasecmp(value, "number") == 0)
1225       interface_format = if_number;
1226     else {
1227       ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
1228       return -1;
1229     }
1230     return 0;
1231   }
1232
1233   if (strcasecmp(key, "Instances") == 0) {
1234     char *eptr = NULL;
1235     double val = strtod(value, &eptr);
1236
1237     if (*eptr != '\0') {
1238       ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
1239       return 1;
1240     }
1241     if (val <= 0) {
1242       ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
1243       return 1;
1244     }
1245     if (val > NR_INSTANCES_MAX) {
1246       ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
1247                         " use a lower setting or recompile the plugin.",
1248             val, NR_INSTANCES_MAX);
1249       return 1;
1250     }
1251
1252     nr_instances = (int)val;
1253     DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
1254     return 0;
1255   }
1256
1257   if (strcasecmp(key, "ExtraStats") == 0) {
1258     char *localvalue = strdup(value);
1259     if (localvalue != NULL) {
1260       char *exstats[EX_STATS_MAX_FIELDS];
1261       int numexstats =
1262           strsplit(localvalue, exstats, STATIC_ARRAY_SIZE(exstats));
1263       extra_stats = parse_ex_stats_flags(exstats, numexstats);
1264       sfree(localvalue);
1265
1266 #ifdef HAVE_JOB_STATS
1267       if ((extra_stats & ex_stats_job_stats_completed) &&
1268           (extra_stats & ex_stats_job_stats_background)) {
1269         ERROR(PLUGIN_NAME " plugin: Invalid job stats configuration. Only one "
1270                           "type of job statistics can be collected at the same "
1271                           "time");
1272         return 1;
1273       }
1274 #endif
1275     }
1276   }
1277
1278   if (strcasecmp(key, "PersistentNotification") == 0) {
1279     persistent_notification = IS_TRUE(value);
1280     return 0;
1281   }
1282
1283   /* Unrecognised option. */
1284   return -1;
1285 }
1286
1287 static int lv_connect(void) {
1288   if (conn == NULL) {
1289 /* `conn_string == NULL' is acceptable */
1290 #ifdef HAVE_FS_INFO
1291     /* virDomainGetFSInfo requires full read-write access connection */
1292     if (extra_stats & ex_stats_fs_info)
1293       conn = virConnectOpen(conn_string);
1294     else
1295 #endif
1296       conn = virConnectOpenReadOnly(conn_string);
1297     if (conn == NULL) {
1298       c_complain(LOG_ERR, &conn_complain,
1299                  PLUGIN_NAME " plugin: Unable to connect: "
1300                              "virConnectOpen failed.");
1301       return -1;
1302     }
1303     int status = virNodeGetInfo(conn, &nodeinfo);
1304     if (status != 0) {
1305       ERROR(PLUGIN_NAME ": virNodeGetInfo failed");
1306       return -1;
1307     }
1308   }
1309   c_release(LOG_NOTICE, &conn_complain,
1310             PLUGIN_NAME " plugin: Connection established.");
1311   return 0;
1312 }
1313
1314 static void lv_disconnect(void) {
1315   if (conn != NULL)
1316     virConnectClose(conn);
1317   conn = NULL;
1318   WARNING(PLUGIN_NAME " plugin: closed connection to libvirt");
1319 }
1320
1321 static int lv_domain_block_info(virDomainPtr dom, const char *path,
1322                                 struct lv_block_info *binfo) {
1323 #ifdef HAVE_BLOCK_STATS_FLAGS
1324   int nparams = 0;
1325   if (virDomainBlockStatsFlags(dom, path, NULL, &nparams, 0) < 0 ||
1326       nparams <= 0) {
1327     VIRT_ERROR(conn, "getting the disk params count");
1328     return -1;
1329   }
1330
1331   virTypedParameterPtr params = calloc((size_t)nparams, sizeof(*params));
1332   if (params == NULL) {
1333     ERROR("virt plugin: alloc(%i) for block=%s parameters failed.", nparams,
1334           path);
1335     return -1;
1336   }
1337
1338   int rc = -1;
1339   if (virDomainBlockStatsFlags(dom, path, params, &nparams, 0) < 0) {
1340     VIRT_ERROR(conn, "getting the disk params values");
1341   } else {
1342     rc = get_block_info(binfo, params, nparams);
1343   }
1344
1345   virTypedParamsClear(params, nparams);
1346   sfree(params);
1347   return rc;
1348 #else
1349   return virDomainBlockStats(dom, path, &(binfo->bi), sizeof(binfo->bi));
1350 #endif /* HAVE_BLOCK_STATS_FLAGS */
1351 }
1352
1353 #ifdef HAVE_PERF_STATS
1354 static void perf_submit(virDomainStatsRecordPtr stats) {
1355   for (int i = 0; i < stats->nparams; ++i) {
1356     /* Replace '.' with '_' in event field to match other metrics' naming
1357      * convention */
1358     char *c = strchr(stats->params[i].field, '.');
1359     if (c)
1360       *c = '_';
1361     submit(stats->dom, "perf", stats->params[i].field,
1362            &(value_t){.derive = stats->params[i].value.ul}, 1);
1363   }
1364 }
1365
1366 static int get_perf_events(virDomainPtr domain) {
1367   virDomainStatsRecordPtr *stats = NULL;
1368   /* virDomainListGetStats requires a NULL terminated list of domains */
1369   virDomainPtr domain_array[] = {domain, NULL};
1370
1371   int status =
1372       virDomainListGetStats(domain_array, VIR_DOMAIN_STATS_PERF, &stats, 0);
1373   if (status == -1) {
1374     ERROR("virt plugin: virDomainListGetStats failed with status %i.", status);
1375     return status;
1376   }
1377
1378   for (int i = 0; i < status; ++i)
1379     perf_submit(stats[i]);
1380
1381   virDomainStatsRecordListFree(stats);
1382   return 0;
1383 }
1384 #endif /* HAVE_PERF_STATS */
1385
1386 static void vcpu_pin_submit(virDomainPtr dom, int max_cpus, int vcpu,
1387                             unsigned char *cpu_maps, int cpu_map_len) {
1388   for (int cpu = 0; cpu < max_cpus; ++cpu) {
1389     char type_instance[DATA_MAX_NAME_LEN];
1390     _Bool is_set = VIR_CPU_USABLE(cpu_maps, cpu_map_len, vcpu, cpu) ? 1 : 0;
1391
1392     snprintf(type_instance, sizeof(type_instance), "vcpu_%d-cpu_%d", vcpu, cpu);
1393     submit(dom, "cpu_affinity", type_instance, &(value_t){.gauge = is_set}, 1);
1394   }
1395 }
1396
1397 static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
1398   int max_cpus = VIR_NODEINFO_MAXCPUS(nodeinfo);
1399   int cpu_map_len = VIR_CPU_MAPLEN(max_cpus);
1400
1401   virVcpuInfoPtr vinfo = calloc(nr_virt_cpu, sizeof(vinfo[0]));
1402   if (vinfo == NULL) {
1403     ERROR(PLUGIN_NAME " plugin: malloc failed.");
1404     return -1;
1405   }
1406
1407   unsigned char *cpumaps = calloc(nr_virt_cpu, cpu_map_len);
1408   if (cpumaps == NULL) {
1409     ERROR(PLUGIN_NAME " plugin: malloc failed.");
1410     sfree(vinfo);
1411     return -1;
1412   }
1413
1414   int status =
1415       virDomainGetVcpus(domain, vinfo, nr_virt_cpu, cpumaps, cpu_map_len);
1416   if (status < 0) {
1417     ERROR(PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.",
1418           status);
1419     sfree(cpumaps);
1420     sfree(vinfo);
1421     return status;
1422   }
1423
1424   for (int i = 0; i < nr_virt_cpu; ++i) {
1425     vcpu_submit(vinfo[i].cpuTime, domain, vinfo[i].number, "virt_vcpu");
1426     if (extra_stats & ex_stats_vcpupin)
1427       vcpu_pin_submit(domain, max_cpus, i, cpumaps, cpu_map_len);
1428   }
1429
1430   sfree(cpumaps);
1431   sfree(vinfo);
1432   return 0;
1433 }
1434
1435 #ifdef HAVE_DOM_REASON
1436
1437 static void domain_state_submit(virDomainPtr dom, int state, int reason) {
1438   value_t values[] = {
1439       {.gauge = (gauge_t)state}, {.gauge = (gauge_t)reason},
1440   };
1441
1442   submit(dom, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
1443 }
1444
1445 static int get_domain_state(virDomainPtr domain) {
1446   int domain_state = 0;
1447   int domain_reason = 0;
1448
1449   int status = virDomainGetState(domain, &domain_state, &domain_reason, 0);
1450   if (status != 0) {
1451     ERROR(PLUGIN_NAME " plugin: virDomainGetState failed with status %i.",
1452           status);
1453     return status;
1454   }
1455
1456   domain_state_submit(domain, domain_state, domain_reason);
1457
1458   return status;
1459 }
1460
1461 #ifdef HAVE_LIST_ALL_DOMAINS
1462 static int get_domain_state_notify(virDomainPtr domain) {
1463   int domain_state = 0;
1464   int domain_reason = 0;
1465
1466   int status = virDomainGetState(domain, &domain_state, &domain_reason, 0);
1467   if (status != 0) {
1468     ERROR(PLUGIN_NAME " plugin: virDomainGetState failed with status %i.",
1469           status);
1470     return status;
1471   }
1472
1473   if (persistent_notification)
1474     domain_state_submit_notif(domain, domain_state, domain_reason);
1475
1476   return status;
1477 }
1478 #endif /* HAVE_LIST_ALL_DOMAINS */
1479 #endif /* HAVE_DOM_REASON */
1480
1481 static int get_memory_stats(virDomainPtr domain) {
1482   virDomainMemoryStatPtr minfo =
1483       calloc(VIR_DOMAIN_MEMORY_STAT_NR, sizeof(virDomainMemoryStatStruct));
1484   if (minfo == NULL) {
1485     ERROR("virt plugin: malloc failed.");
1486     return -1;
1487   }
1488
1489   int mem_stats =
1490       virDomainMemoryStats(domain, minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);
1491   if (mem_stats < 0) {
1492     ERROR("virt plugin: virDomainMemoryStats failed with mem_stats %i.",
1493           mem_stats);
1494     sfree(minfo);
1495     return mem_stats;
1496   }
1497
1498   for (int i = 0; i < mem_stats; i++)
1499     memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
1500
1501   sfree(minfo);
1502   return 0;
1503 }
1504
1505 #ifdef HAVE_DISK_ERR
1506 static void disk_err_submit(virDomainPtr domain,
1507                             virDomainDiskErrorPtr disk_err) {
1508   submit(domain, "disk_error", disk_err->disk,
1509          &(value_t){.gauge = disk_err->error}, 1);
1510 }
1511
1512 static int get_disk_err(virDomainPtr domain) {
1513   /* Get preferred size of disk errors array */
1514   int disk_err_count = virDomainGetDiskErrors(domain, NULL, 0, 0);
1515   if (disk_err_count == -1) {
1516     ERROR(PLUGIN_NAME
1517           " plugin: failed to get preferred size of disk errors array");
1518     return -1;
1519   }
1520
1521   DEBUG(PLUGIN_NAME
1522         " plugin: preferred size of disk errors array: %d for domain %s",
1523         disk_err_count, virDomainGetName(domain));
1524   virDomainDiskError disk_err[disk_err_count];
1525
1526   disk_err_count = virDomainGetDiskErrors(domain, disk_err, disk_err_count, 0);
1527   if (disk_err_count == -1) {
1528     ERROR(PLUGIN_NAME " plugin: virDomainGetDiskErrors failed with status %d",
1529           disk_err_count);
1530     return -1;
1531   }
1532
1533   DEBUG(PLUGIN_NAME " plugin: detected %d disk errors in domain %s",
1534         disk_err_count, virDomainGetName(domain));
1535
1536   for (int i = 0; i < disk_err_count; ++i) {
1537     disk_err_submit(domain, &disk_err[i]);
1538     sfree(disk_err[i].disk);
1539   }
1540
1541   return 0;
1542 }
1543 #endif /* HAVE_DISK_ERR */
1544
1545 static int get_block_stats(struct block_device *block_dev) {
1546
1547   if (!block_dev) {
1548     ERROR(PLUGIN_NAME " plugin: get_block_stats NULL pointer");
1549     return -1;
1550   }
1551
1552   struct lv_block_info binfo;
1553   init_block_info(&binfo);
1554
1555   if (lv_domain_block_info(block_dev->dom, block_dev->path, &binfo) < 0) {
1556     ERROR(PLUGIN_NAME " plugin: lv_domain_block_info failed");
1557     return -1;
1558   }
1559
1560   disk_submit(&binfo, block_dev->dom, block_dev->path);
1561   return 0;
1562 }
1563
1564 #ifdef HAVE_FS_INFO
1565
1566 #define NM_ADD_ITEM(_fun, _name, _val)                                         \
1567   do {                                                                         \
1568     ret = _fun(&notif, _name, _val);                                           \
1569     if (ret != 0) {                                                            \
1570       ERROR(PLUGIN_NAME " plugin: failed to add notification metadata");       \
1571       goto cleanup;                                                            \
1572     }                                                                          \
1573   } while (0)
1574
1575 #define NM_ADD_STR_ITEMS(_items, _size)                                        \
1576   do {                                                                         \
1577     for (int _i = 0; _i < _size; ++_i) {                                       \
1578       DEBUG(PLUGIN_NAME                                                        \
1579             " plugin: Adding notification metadata name=%s value=%s",          \
1580             _items[_i].name, _items[_i].value);                                \
1581       NM_ADD_ITEM(plugin_notification_meta_add_string, _items[_i].name,        \
1582                   _items[_i].value);                                           \
1583     }                                                                          \
1584   } while (0)
1585
1586 static int fs_info_notify(virDomainPtr domain, virDomainFSInfoPtr fs_info) {
1587   notification_t notif;
1588   int ret = 0;
1589
1590   /* Local struct, just for the purpose of this function. */
1591   typedef struct nm_str_item_s {
1592     const char *name;
1593     const char *value;
1594   } nm_str_item_t;
1595
1596   nm_str_item_t fs_dev_alias[fs_info->ndevAlias];
1597   nm_str_item_t fs_str_items[] = {
1598       {.name = "mountpoint", .value = fs_info->mountpoint},
1599       {.name = "name", .value = fs_info->name},
1600       {.name = "fstype", .value = fs_info->fstype}};
1601
1602   for (int i = 0; i < fs_info->ndevAlias; ++i) {
1603     fs_dev_alias[i].name = "devAlias";
1604     fs_dev_alias[i].value = fs_info->devAlias[i];
1605   }
1606
1607   init_notif(&notif, domain, NOTIF_OKAY, "File system information",
1608              "file_system", NULL);
1609   NM_ADD_STR_ITEMS(fs_str_items, STATIC_ARRAY_SIZE(fs_str_items));
1610   NM_ADD_ITEM(plugin_notification_meta_add_unsigned_int, "ndevAlias",
1611               fs_info->ndevAlias);
1612   NM_ADD_STR_ITEMS(fs_dev_alias, fs_info->ndevAlias);
1613
1614   plugin_dispatch_notification(&notif);
1615
1616 cleanup:
1617   if (notif.meta)
1618     plugin_notification_meta_free(notif.meta);
1619   return ret;
1620 }
1621
1622 #undef RETURN_ON_ERR
1623 #undef NM_ADD_STR_ITEMS
1624
1625 static int get_fs_info(virDomainPtr domain) {
1626   virDomainFSInfoPtr *fs_info = NULL;
1627   int ret = 0;
1628
1629   int mount_points_cnt = virDomainGetFSInfo(domain, &fs_info, 0);
1630   if (mount_points_cnt == -1) {
1631     ERROR(PLUGIN_NAME " plugin: virDomainGetFSInfo failed: %d",
1632           mount_points_cnt);
1633     return mount_points_cnt;
1634   }
1635
1636   for (int i = 0; i < mount_points_cnt; ++i) {
1637     if (fs_info_notify(domain, fs_info[i]) != 0) {
1638       ERROR(PLUGIN_NAME " plugin: failed to send file system notification "
1639                         "for mount point %s",
1640             fs_info[i]->mountpoint);
1641       ret = -1;
1642     }
1643     virDomainFSInfoFree(fs_info[i]);
1644   }
1645
1646   sfree(fs_info);
1647   return ret;
1648 }
1649
1650 #endif /* HAVE_FS_INFO */
1651
1652 #ifdef HAVE_JOB_STATS
1653 static void job_stats_submit(virDomainPtr domain, virTypedParameterPtr param) {
1654   value_t vl = {0};
1655
1656   if (param->type == VIR_TYPED_PARAM_INT)
1657     vl.derive = param->value.i;
1658   else if (param->type == VIR_TYPED_PARAM_UINT)
1659     vl.derive = param->value.ui;
1660   else if (param->type == VIR_TYPED_PARAM_LLONG)
1661     vl.derive = param->value.l;
1662   else if (param->type == VIR_TYPED_PARAM_ULLONG)
1663     vl.derive = param->value.ul;
1664   else if (param->type == VIR_TYPED_PARAM_DOUBLE)
1665     vl.derive = param->value.d;
1666   else if (param->type == VIR_TYPED_PARAM_BOOLEAN)
1667     vl.derive = param->value.b;
1668   else if (param->type == VIR_TYPED_PARAM_STRING) {
1669     submit_notif(domain, NOTIF_OKAY, param->value.s, "job_stats", param->field);
1670     return;
1671   } else {
1672     ERROR(PLUGIN_NAME " plugin: unrecognized virTypedParameterType");
1673     return;
1674   }
1675
1676   submit(domain, "job_stats", param->field, &vl, 1);
1677 }
1678
1679 static int get_job_stats(virDomainPtr domain) {
1680   int ret = 0;
1681   int job_type = 0;
1682   int nparams = 0;
1683   virTypedParameterPtr params = NULL;
1684   int flags = (extra_stats & ex_stats_job_stats_completed)
1685                   ? VIR_DOMAIN_JOB_STATS_COMPLETED
1686                   : 0;
1687
1688   ret = virDomainGetJobStats(domain, &job_type, &params, &nparams, flags);
1689   if (ret != 0) {
1690     ERROR(PLUGIN_NAME " plugin: virDomainGetJobStats failed: %d", ret);
1691     return ret;
1692   }
1693
1694   DEBUG(PLUGIN_NAME " plugin: job_type=%d nparams=%d", job_type, nparams);
1695
1696   for (int i = 0; i < nparams; ++i) {
1697     DEBUG(PLUGIN_NAME " plugin: param[%d] field=%s type=%d", i, params[i].field,
1698           params[i].type);
1699     job_stats_submit(domain, &params[i]);
1700   }
1701
1702   virTypedParamsFree(params, nparams);
1703   return ret;
1704 }
1705 #endif /* HAVE_JOB_STATS */
1706
1707 static int get_domain_metrics(domain_t *domain) {
1708   struct lv_info info;
1709
1710   if (!domain || !domain->ptr) {
1711     ERROR(PLUGIN_NAME ": get_domain_metrics: NULL pointer");
1712     return -1;
1713   }
1714
1715   init_lv_info(&info);
1716   int status = lv_domain_info(domain->ptr, &info);
1717   if (status != 0) {
1718     ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
1719           status);
1720     return -1;
1721   }
1722
1723   if (extra_stats & ex_stats_domain_state) {
1724 #ifdef HAVE_DOM_REASON
1725     /* At this point we already know domain's state from virDomainGetInfo call,
1726      * however it doesn't provide a reason for entering particular state.
1727      * We need to get it from virDomainGetState.
1728      */
1729     GET_STATS(get_domain_state, "domain reason", domain->ptr);
1730 #endif
1731   }
1732
1733   /* Gather remaining stats only for running domains */
1734   if (info.di.state != VIR_DOMAIN_RUNNING)
1735     return 0;
1736
1737   pcpu_submit(domain->ptr, &info);
1738   cpu_submit(domain, info.di.cpuTime);
1739
1740   memory_submit(domain->ptr, (gauge_t)info.di.memory * 1024);
1741
1742   GET_STATS(get_vcpu_stats, "vcpu stats", domain->ptr, info.di.nrVirtCpu);
1743   GET_STATS(get_memory_stats, "memory stats", domain->ptr);
1744
1745 #ifdef HAVE_PERF_STATS
1746   if (extra_stats & ex_stats_perf)
1747     GET_STATS(get_perf_events, "performance monitoring events", domain->ptr);
1748 #endif
1749
1750 #ifdef HAVE_FS_INFO
1751   if (extra_stats & ex_stats_fs_info)
1752     GET_STATS(get_fs_info, "file system info", domain->ptr);
1753 #endif
1754
1755 #ifdef HAVE_DISK_ERR
1756   if (extra_stats & ex_stats_disk_err)
1757     GET_STATS(get_disk_err, "disk errors", domain->ptr);
1758 #endif
1759
1760 #ifdef HAVE_JOB_STATS
1761   if (extra_stats &
1762       (ex_stats_job_stats_completed | ex_stats_job_stats_background))
1763     GET_STATS(get_job_stats, "job stats", domain->ptr);
1764 #endif
1765
1766   /* Update cached virDomainInfo. It has to be done after cpu_submit */
1767   memcpy(&domain->info, &info.di, sizeof(domain->info));
1768
1769   return 0;
1770 }
1771
1772 static int get_if_dev_stats(struct interface_device *if_dev) {
1773   virDomainInterfaceStatsStruct stats = {0};
1774   char *display_name = NULL;
1775
1776   if (!if_dev) {
1777     ERROR(PLUGIN_NAME " plugin: get_if_dev_stats: NULL pointer");
1778     return -1;
1779   }
1780
1781   switch (interface_format) {
1782   case if_address:
1783     display_name = if_dev->address;
1784     break;
1785   case if_number:
1786     display_name = if_dev->number;
1787     break;
1788   case if_name:
1789   default:
1790     display_name = if_dev->path;
1791   }
1792
1793   if (virDomainInterfaceStats(if_dev->dom, if_dev->path, &stats,
1794                               sizeof(stats)) != 0) {
1795     ERROR(PLUGIN_NAME " plugin: virDomainInterfaceStats failed");
1796     return -1;
1797   }
1798
1799   if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
1800     submit_derive2("if_octets", (derive_t)stats.rx_bytes,
1801                    (derive_t)stats.tx_bytes, if_dev->dom, display_name);
1802
1803   if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
1804     submit_derive2("if_packets", (derive_t)stats.rx_packets,
1805                    (derive_t)stats.tx_packets, if_dev->dom, display_name);
1806
1807   if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
1808     submit_derive2("if_errors", (derive_t)stats.rx_errs,
1809                    (derive_t)stats.tx_errs, if_dev->dom, display_name);
1810
1811   if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
1812     submit_derive2("if_dropped", (derive_t)stats.rx_drop,
1813                    (derive_t)stats.tx_drop, if_dev->dom, display_name);
1814   return 0;
1815 }
1816
1817 static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr conn,
1818                                      virDomainPtr dom, int event, int detail,
1819                                      __attribute__((unused)) void *opaque) {
1820   int domain_state = map_domain_event_to_state(event);
1821   int domain_reason = map_domain_event_detail_to_reason(event, detail);
1822   domain_state_submit_notif(dom, domain_state, domain_reason);
1823
1824   return 0;
1825 }
1826
1827 static int register_event_impl(void) {
1828   if (virEventRegisterDefaultImpl() < 0) {
1829     virErrorPtr err = virGetLastError();
1830     ERROR(PLUGIN_NAME
1831           " plugin: error while event implementation registering: %s",
1832           err && err->message ? err->message : "Unknown error");
1833     return -1;
1834   }
1835
1836   return 0;
1837 }
1838
1839 /* worker function running default event implementation */
1840 static void *event_loop_worker(__attribute__((unused)) void *arg) {
1841   while (1) {
1842     if (virEventRunDefaultImpl() < 0) {
1843       virErrorPtr err = virGetLastError();
1844       ERROR(PLUGIN_NAME " plugin: failed to run event loop: %s\n",
1845             err && err->message ? err->message : "Unknown error");
1846     }
1847   }
1848
1849   return NULL;
1850 }
1851
1852 /* register domain event callback and start event loop thread */
1853 static int start_event_loop(void) {
1854   domain_event_cb_id = virConnectDomainEventRegisterAny(
1855       conn, NULL, VIR_DOMAIN_EVENT_ID_LIFECYCLE,
1856       VIR_DOMAIN_EVENT_CALLBACK(domain_lifecycle_event_cb), NULL, NULL);
1857   if (domain_event_cb_id == -1) {
1858     ERROR(PLUGIN_NAME " plugin: error while callback registering");
1859     return -1;
1860   }
1861
1862   if (pthread_create(&event_loop_tid, NULL, event_loop_worker, NULL)) {
1863     ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
1864     virConnectDomainEventDeregisterAny(conn, domain_event_cb_id);
1865     return -1;
1866   }
1867
1868   return 0;
1869 }
1870
1871 /* stop event loop thread and deregister callback */
1872 static void stop_event_loop(void) {
1873   if (pthread_cancel(event_loop_tid) != 0)
1874     ERROR(PLUGIN_NAME " plugin: cancelling thread %lu failed", event_loop_tid);
1875
1876   if (pthread_join(event_loop_tid, NULL) != 0)
1877     ERROR(PLUGIN_NAME " plugin: stopping thread %lu failed", event_loop_tid);
1878
1879   if (conn != NULL && domain_event_cb_id != -1)
1880     virConnectDomainEventDeregisterAny(conn, domain_event_cb_id);
1881 }
1882
1883 static int persistent_domains_state_notification(void) {
1884   int status = 0;
1885   int n;
1886 #ifdef HAVE_LIST_ALL_DOMAINS
1887   virDomainPtr *domains;
1888   n = virConnectListAllDomains(conn, &domains,
1889                                VIR_CONNECT_GET_ALL_DOMAINS_STATS_PERSISTENT);
1890   if (n < 0) {
1891     VIRT_ERROR(conn, "reading list of persistent domains");
1892     status = -1;
1893   } else {
1894     DEBUG(PLUGIN_NAME " plugin: getting state of %i persistent domains", n);
1895     /* Fetch each persistent domain's state and notify it */
1896     int n_notified = n;
1897     for (int i = 0; i < n; ++i) {
1898       status = get_domain_state_notify(domains[i]);
1899       if (status != 0) {
1900         n_notified--;
1901         ERROR(PLUGIN_NAME " plugin: could not notify state of domain %s",
1902               virDomainGetName(domains[i]));
1903       }
1904     }
1905
1906     sfree(domains);
1907     DEBUG(PLUGIN_NAME " plugin: notified state of %i persistent domains",
1908           n_notified);
1909   }
1910 #else
1911   n = virConnectNumOfDomains(conn);
1912   if (n > 0) {
1913     int *domids;
1914     /* Get list of domains. */
1915     domids = malloc(sizeof(*domids) * n);
1916     if (domids == NULL) {
1917       ERROR(PLUGIN_NAME " plugin: malloc failed.");
1918       return -1;
1919     }
1920     n = virConnectListDomains(conn, domids, n);
1921     if (n < 0) {
1922       VIRT_ERROR(conn, "reading list of domains");
1923       sfree(domids);
1924       return -1;
1925     }
1926     /* Fetch info of each active domain and notify it */
1927     for (int i = 0; i < n; ++i) {
1928       virDomainInfo info;
1929       virDomainPtr dom = NULL;
1930       dom = virDomainLookupByID(conn, domids[i]);
1931       if (dom == NULL) {
1932         VIRT_ERROR(conn, "virDomainLookupByID");
1933         /* Could be that the domain went away -- ignore it anyway. */
1934         continue;
1935       }
1936       status = virDomainGetInfo(dom, &info);
1937       if (status != 0) {
1938         ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
1939               status);
1940         continue;
1941       }
1942       /* virDomainGetState is not available. Submit 0, which corresponds to
1943        * unknown reason. */
1944       domain_state_submit_notif(dom, info.state, 0);
1945     }
1946     sfree(domids);
1947   }
1948 #endif
1949
1950   return status;
1951 }
1952
1953 static int lv_read(user_data_t *ud) {
1954   time_t t;
1955   struct lv_read_instance *inst = NULL;
1956   struct lv_read_state *state = NULL;
1957
1958   if (ud->data == NULL) {
1959     ERROR(PLUGIN_NAME " plugin: NULL userdata");
1960     return -1;
1961   }
1962
1963   inst = ud->data;
1964   state = &inst->read_state;
1965
1966   _Bool reconnect = conn == NULL ? 1 : 0;
1967   /* event implementation must be registered before connection is opened */
1968   if (inst->id == 0) {
1969     if (!persistent_notification && reconnect)
1970       if (register_event_impl() != 0)
1971         return -1;
1972
1973     if (lv_connect() < 0)
1974       return -1;
1975
1976     if (!persistent_notification && reconnect && conn != NULL)
1977       if (start_event_loop() != 0)
1978         return -1;
1979   }
1980
1981   time(&t);
1982
1983   /* Need to refresh domain or device lists? */
1984   if ((last_refresh == (time_t)0) ||
1985       ((interval > 0) && ((last_refresh + interval) <= t))) {
1986     if (inst->id == 0 && persistent_notification) {
1987       int status = persistent_domains_state_notification();
1988       if (status != 0)
1989         DEBUG(PLUGIN_NAME " plugin: persistent_domains_state_notifications "
1990                           "returned with status %i",
1991               status);
1992     }
1993     if (refresh_lists(inst) != 0) {
1994       if (inst->id == 0) {
1995         if (!persistent_notification)
1996           stop_event_loop();
1997         lv_disconnect();
1998       }
1999       return -1;
2000     }
2001     last_refresh = t;
2002   }
2003
2004 #if COLLECT_DEBUG
2005   for (int i = 0; i < state->nr_domains; ++i)
2006     DEBUG(PLUGIN_NAME " plugin: domain %s",
2007           virDomainGetName(state->domains[i].ptr));
2008   for (int i = 0; i < state->nr_block_devices; ++i)
2009     DEBUG(PLUGIN_NAME " plugin: block device %d %s:%s", i,
2010           virDomainGetName(state->block_devices[i].dom),
2011           state->block_devices[i].path);
2012   for (int i = 0; i < state->nr_interface_devices; ++i)
2013     DEBUG(PLUGIN_NAME " plugin: interface device %d %s:%s", i,
2014           virDomainGetName(state->interface_devices[i].dom),
2015           state->interface_devices[i].path);
2016 #endif
2017
2018   /* Get domains' metrics */
2019   for (int i = 0; i < state->nr_domains; ++i) {
2020     domain_t *dom = &state->domains[i];
2021     int status;
2022     if (dom->active)
2023       status = get_domain_metrics(dom);
2024     else
2025       status = get_domain_state(dom->ptr);
2026
2027     if (status != 0)
2028       ERROR(PLUGIN_NAME " failed to get metrics for domain=%s",
2029             virDomainGetName(dom->ptr));
2030   }
2031
2032   /* Get block device stats for each domain. */
2033   for (int i = 0; i < state->nr_block_devices; ++i) {
2034     int status = get_block_stats(&state->block_devices[i]);
2035     if (status != 0)
2036       ERROR(PLUGIN_NAME
2037             " failed to get stats for block device (%s) in domain %s",
2038             state->block_devices[i].path,
2039             virDomainGetName(state->domains[i].ptr));
2040   }
2041
2042   /* Get interface stats for each domain. */
2043   for (int i = 0; i < state->nr_interface_devices; ++i) {
2044     int status = get_if_dev_stats(&state->interface_devices[i]);
2045     if (status != 0)
2046       ERROR(PLUGIN_NAME
2047             " failed to get interface stats for device (%s) in domain %s",
2048             state->interface_devices[i].path,
2049             virDomainGetName(state->interface_devices[i].dom));
2050   }
2051
2052   return 0;
2053 }
2054
2055 static int lv_init_instance(size_t i, plugin_read_cb callback) {
2056   struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
2057   struct lv_read_instance *inst = &(lv_ud->inst);
2058
2059   memset(lv_ud, 0, sizeof(*lv_ud));
2060
2061   snprintf(inst->tag, sizeof(inst->tag), "%s-%" PRIsz, PLUGIN_NAME, i);
2062   inst->id = i;
2063
2064   user_data_t *ud = &(lv_ud->ud);
2065   ud->data = inst;
2066   ud->free_func = NULL;
2067
2068   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
2069
2070   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
2071 }
2072
2073 static void lv_clean_read_state(struct lv_read_state *state) {
2074   free_block_devices(state);
2075   free_interface_devices(state);
2076   free_domains(state);
2077 }
2078
2079 static void lv_fini_instance(size_t i) {
2080   struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
2081   struct lv_read_state *state = &(inst->read_state);
2082
2083   lv_clean_read_state(state);
2084
2085   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
2086 }
2087
2088 static int lv_init(void) {
2089   if (virInitialize() != 0)
2090     return -1;
2091
2092   /* event implementation must be registered before connection is opened */
2093   if (!persistent_notification)
2094     if (register_event_impl() != 0)
2095       return -1;
2096
2097   if (lv_connect() != 0)
2098     return -1;
2099
2100   DEBUG(PLUGIN_NAME " plugin: starting event loop");
2101
2102   if (!persistent_notification)
2103     if (start_event_loop() != 0)
2104       return -1;
2105
2106   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
2107
2108   for (int i = 0; i < nr_instances; ++i)
2109     if (lv_init_instance(i, lv_read) != 0)
2110       return -1;
2111
2112   return 0;
2113 }
2114
2115 /*
2116  * returns 0 on success and <0 on error
2117  */
2118 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
2119                              char *dom_tag) {
2120   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
2121   xmlXPathObjectPtr xpath_obj = NULL;
2122   xmlNodePtr xml_node = NULL;
2123   int ret = -1;
2124   int err;
2125
2126   err = xmlXPathRegisterNs(xpath_ctx,
2127                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
2128                            (const xmlChar *)METADATA_VM_PARTITION_URI);
2129   if (err) {
2130     ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
2131           METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
2132     goto done;
2133   }
2134
2135   snprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
2136            METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
2137   xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
2138   if (xpath_obj == NULL) {
2139     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
2140           xpath_str, dom_name);
2141     goto done;
2142   }
2143
2144   if (xpath_obj->type != XPATH_NODESET) {
2145     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
2146                       "(wanted %d) on domain %s",
2147           xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
2148     goto done;
2149   }
2150
2151   /*
2152    * from now on there is no real error, it's ok if a domain
2153    * doesn't have the metadata partition tag.
2154    */
2155   ret = 0;
2156   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
2157     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
2158                       "expected=1 on domain %s",
2159           xpath_str,
2160           (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
2161           dom_name);
2162   } else {
2163     xml_node = xpath_obj->nodesetval->nodeTab[0];
2164     sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
2165   }
2166
2167 done:
2168   /* deregister to clean up */
2169   err = xmlXPathRegisterNs(xpath_ctx,
2170                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
2171   if (err) {
2172     /* we can't really recover here */
2173     ERROR(PLUGIN_NAME
2174           " plugin: deregistration of namespace %s failed for domain %s",
2175           METADATA_VM_PARTITION_PREFIX, dom_name);
2176   }
2177   if (xpath_obj)
2178     xmlXPathFreeObject(xpath_obj);
2179
2180   return ret;
2181 }
2182
2183 static int is_known_tag(const char *dom_tag) {
2184   for (int i = 0; i < nr_instances; ++i)
2185     if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
2186       return 1;
2187   return 0;
2188 }
2189
2190 static int lv_instance_include_domain(struct lv_read_instance *inst,
2191                                       const char *dom_name,
2192                                       const char *dom_tag) {
2193   if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
2194     return 1;
2195
2196   /* instance#0 will always be there, so it is in charge of extra duties */
2197   if (inst->id == 0) {
2198     if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
2199       DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
2200                         "with unknown tag '%s'",
2201             inst->tag, dom_name, dom_tag);
2202       return 1;
2203     }
2204   }
2205
2206   return 0;
2207 }
2208
2209 static int refresh_lists(struct lv_read_instance *inst) {
2210   struct lv_read_state *state = &inst->read_state;
2211   int n;
2212
2213 #ifndef HAVE_LIST_ALL_DOMAINS
2214   n = virConnectNumOfDomains(conn);
2215   if (n < 0) {
2216     VIRT_ERROR(conn, "reading number of domains");
2217     return -1;
2218   }
2219 #endif
2220
2221   lv_clean_read_state(state);
2222
2223 #ifndef HAVE_LIST_ALL_DOMAINS
2224   if (n == 0)
2225     goto end;
2226 #endif
2227
2228 #ifdef HAVE_LIST_ALL_DOMAINS
2229   virDomainPtr *domains, *domains_inactive;
2230   int m = virConnectListAllDomains(conn, &domains_inactive,
2231                                    VIR_CONNECT_LIST_DOMAINS_INACTIVE);
2232   n = virConnectListAllDomains(conn, &domains, VIR_CONNECT_LIST_DOMAINS_ACTIVE);
2233 #else
2234   int *domids;
2235
2236   /* Get list of domains. */
2237   domids = malloc(sizeof(*domids) * n);
2238   if (domids == NULL) {
2239     ERROR(PLUGIN_NAME " plugin: malloc failed.");
2240     return -1;
2241   }
2242
2243   n = virConnectListDomains(conn, domids, n);
2244 #endif
2245
2246   if (n < 0) {
2247     VIRT_ERROR(conn, "reading list of domains");
2248 #ifndef HAVE_LIST_ALL_DOMAINS
2249     sfree(domids);
2250 #else
2251     sfree(domains_inactive);
2252 #endif
2253     return -1;
2254   }
2255
2256 #ifdef HAVE_LIST_ALL_DOMAINS
2257   for (int i = 0; i < m; ++i)
2258     if (add_domain(state, domains_inactive[i], 0) < 0) {
2259       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2260       continue;
2261     }
2262 #endif
2263
2264   /* Fetch each domain and add it to the list, unless ignore. */
2265   for (int i = 0; i < n; ++i) {
2266     const char *name;
2267     char *xml = NULL;
2268     xmlDocPtr xml_doc = NULL;
2269     xmlXPathContextPtr xpath_ctx = NULL;
2270     xmlXPathObjectPtr xpath_obj = NULL;
2271     char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
2272     virDomainInfo info;
2273     int status;
2274
2275 #ifdef HAVE_LIST_ALL_DOMAINS
2276     virDomainPtr dom = domains[i];
2277 #else
2278     virDomainPtr dom = NULL;
2279     dom = virDomainLookupByID(conn, domids[i]);
2280     if (dom == NULL) {
2281       VIRT_ERROR(conn, "virDomainLookupByID");
2282       /* Could be that the domain went away -- ignore it anyway. */
2283       continue;
2284     }
2285 #endif
2286
2287     name = virDomainGetName(dom);
2288     if (name == NULL) {
2289       VIRT_ERROR(conn, "virDomainGetName");
2290       goto cont;
2291     }
2292
2293     status = virDomainGetInfo(dom, &info);
2294     if (status != 0) {
2295       ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
2296             status);
2297       continue;
2298     }
2299
2300     if (info.state != VIR_DOMAIN_RUNNING) {
2301       DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", name);
2302       continue;
2303     }
2304
2305     if (il_domains && ignorelist_match(il_domains, name) != 0)
2306       goto cont;
2307
2308     /* Get a list of devices for this domain. */
2309     xml = virDomainGetXMLDesc(dom, 0);
2310     if (!xml) {
2311       VIRT_ERROR(conn, "virDomainGetXMLDesc");
2312       goto cont;
2313     }
2314
2315     /* Yuck, XML.  Parse out the devices. */
2316     xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
2317     if (xml_doc == NULL) {
2318       VIRT_ERROR(conn, "xmlReadDoc");
2319       goto cont;
2320     }
2321
2322     xpath_ctx = xmlXPathNewContext(xml_doc);
2323
2324     if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
2325       ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
2326       goto cont;
2327     }
2328
2329     if (!lv_instance_include_domain(inst, name, tag))
2330       goto cont;
2331
2332     if (add_domain(state, dom, 1) < 0) {
2333       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2334       goto cont;
2335     }
2336
2337     /* Block devices. */
2338     const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
2339     if (blockdevice_format == source)
2340       bd_xmlpath = "/domain/devices/disk/source[@dev]";
2341     xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
2342
2343     if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
2344         xpath_obj->nodesetval == NULL)
2345       goto cont;
2346
2347     for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
2348       xmlNodePtr node;
2349       char *path = NULL;
2350
2351       node = xpath_obj->nodesetval->nodeTab[j];
2352       if (!node)
2353         continue;
2354       path = (char *)xmlGetProp(node, (xmlChar *)"dev");
2355       if (!path)
2356         continue;
2357
2358       if (il_block_devices &&
2359           ignore_device_match(il_block_devices, name, path) != 0)
2360         goto cont2;
2361
2362       add_block_device(state, dom, path);
2363     cont2:
2364       if (path)
2365         xmlFree(path);
2366     }
2367     xmlXPathFreeObject(xpath_obj);
2368
2369     /* Network interfaces. */
2370     xpath_obj = xmlXPathEval(
2371         (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
2372     if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
2373         xpath_obj->nodesetval == NULL)
2374       goto cont;
2375
2376     xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
2377
2378     for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
2379       char *path = NULL;
2380       char *address = NULL;
2381       xmlNodePtr xml_interface;
2382
2383       xml_interface = xml_interfaces->nodeTab[j];
2384       if (!xml_interface)
2385         continue;
2386
2387       for (xmlNodePtr child = xml_interface->children; child;
2388            child = child->next) {
2389         if (child->type != XML_ELEMENT_NODE)
2390           continue;
2391
2392         if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
2393           path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2394           if (!path)
2395             continue;
2396         } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
2397           address = (char *)xmlGetProp(child, (const xmlChar *)"address");
2398           if (!address)
2399             continue;
2400         }
2401       }
2402
2403       if (il_interface_devices &&
2404           (ignore_device_match(il_interface_devices, name, path) != 0 ||
2405            ignore_device_match(il_interface_devices, name, address) != 0))
2406         goto cont3;
2407
2408       add_interface_device(state, dom, path, address, j + 1);
2409     cont3:
2410       if (path)
2411         xmlFree(path);
2412       if (address)
2413         xmlFree(address);
2414     }
2415
2416   cont:
2417     if (xpath_obj)
2418       xmlXPathFreeObject(xpath_obj);
2419     if (xpath_ctx)
2420       xmlXPathFreeContext(xpath_ctx);
2421     if (xml_doc)
2422       xmlFreeDoc(xml_doc);
2423     sfree(xml);
2424   }
2425
2426 #ifdef HAVE_LIST_ALL_DOMAINS
2427   sfree(domains);
2428   sfree(domains_inactive);
2429 #else
2430   sfree(domids);
2431
2432 end:
2433 #endif
2434
2435   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
2436                     " domains=%i block_devices=%i iface_devices=%i",
2437         inst->tag, state->nr_domains, state->nr_block_devices,
2438         state->nr_interface_devices);
2439
2440   return 0;
2441 }
2442
2443 static void free_domains(struct lv_read_state *state) {
2444   if (state->domains) {
2445     for (int i = 0; i < state->nr_domains; ++i)
2446       virDomainFree(state->domains[i].ptr);
2447     sfree(state->domains);
2448   }
2449   state->domains = NULL;
2450   state->nr_domains = 0;
2451 }
2452
2453 static int add_domain(struct lv_read_state *state, virDomainPtr dom,
2454                       _Bool active) {
2455   domain_t *new_ptr;
2456   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
2457
2458   if (state->domains)
2459     new_ptr = realloc(state->domains, new_size);
2460   else
2461     new_ptr = malloc(new_size);
2462
2463   if (new_ptr == NULL)
2464     return -1;
2465
2466   state->domains = new_ptr;
2467   state->domains[state->nr_domains].ptr = dom;
2468   state->domains[state->nr_domains].active = active;
2469   memset(&state->domains[state->nr_domains].info, 0,
2470          sizeof(state->domains[state->nr_domains].info));
2471
2472   return state->nr_domains++;
2473 }
2474
2475 static void free_block_devices(struct lv_read_state *state) {
2476   if (state->block_devices) {
2477     for (int i = 0; i < state->nr_block_devices; ++i)
2478       sfree(state->block_devices[i].path);
2479     sfree(state->block_devices);
2480   }
2481   state->block_devices = NULL;
2482   state->nr_block_devices = 0;
2483 }
2484
2485 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
2486                             const char *path) {
2487   struct block_device *new_ptr;
2488   int new_size =
2489       sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
2490   char *path_copy;
2491
2492   path_copy = strdup(path);
2493   if (!path_copy)
2494     return -1;
2495
2496   if (state->block_devices)
2497     new_ptr = realloc(state->block_devices, new_size);
2498   else
2499     new_ptr = malloc(new_size);
2500
2501   if (new_ptr == NULL) {
2502     sfree(path_copy);
2503     return -1;
2504   }
2505   state->block_devices = new_ptr;
2506   state->block_devices[state->nr_block_devices].dom = dom;
2507   state->block_devices[state->nr_block_devices].path = path_copy;
2508   return state->nr_block_devices++;
2509 }
2510
2511 static void free_interface_devices(struct lv_read_state *state) {
2512   if (state->interface_devices) {
2513     for (int i = 0; i < state->nr_interface_devices; ++i) {
2514       sfree(state->interface_devices[i].path);
2515       sfree(state->interface_devices[i].address);
2516       sfree(state->interface_devices[i].number);
2517     }
2518     sfree(state->interface_devices);
2519   }
2520   state->interface_devices = NULL;
2521   state->nr_interface_devices = 0;
2522 }
2523
2524 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
2525                                 const char *path, const char *address,
2526                                 unsigned int number) {
2527   struct interface_device *new_ptr;
2528   int new_size =
2529       sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
2530   char *path_copy, *address_copy, number_string[15];
2531
2532   if ((path == NULL) || (address == NULL))
2533     return EINVAL;
2534
2535   path_copy = strdup(path);
2536   if (!path_copy)
2537     return -1;
2538
2539   address_copy = strdup(address);
2540   if (!address_copy) {
2541     sfree(path_copy);
2542     return -1;
2543   }
2544
2545   snprintf(number_string, sizeof(number_string), "interface-%u", number);
2546
2547   if (state->interface_devices)
2548     new_ptr = realloc(state->interface_devices, new_size);
2549   else
2550     new_ptr = malloc(new_size);
2551
2552   if (new_ptr == NULL) {
2553     sfree(path_copy);
2554     sfree(address_copy);
2555     return -1;
2556   }
2557   state->interface_devices = new_ptr;
2558   state->interface_devices[state->nr_interface_devices].dom = dom;
2559   state->interface_devices[state->nr_interface_devices].path = path_copy;
2560   state->interface_devices[state->nr_interface_devices].address = address_copy;
2561   state->interface_devices[state->nr_interface_devices].number =
2562       strdup(number_string);
2563   return state->nr_interface_devices++;
2564 }
2565
2566 static int ignore_device_match(ignorelist_t *il, const char *domname,
2567                                const char *devpath) {
2568   char *name;
2569   int n, r;
2570
2571   if ((domname == NULL) || (devpath == NULL))
2572     return 0;
2573
2574   n = strlen(domname) + strlen(devpath) + 2;
2575   name = malloc(n);
2576   if (name == NULL) {
2577     ERROR(PLUGIN_NAME " plugin: malloc failed.");
2578     return 0;
2579   }
2580   snprintf(name, n, "%s:%s", domname, devpath);
2581   r = ignorelist_match(il, name);
2582   sfree(name);
2583   return r;
2584 }
2585
2586 static int lv_shutdown(void) {
2587   for (int i = 0; i < nr_instances; ++i) {
2588     lv_fini_instance(i);
2589   }
2590
2591   DEBUG(PLUGIN_NAME " plugin: stopping event loop");
2592
2593   if (!persistent_notification)
2594     stop_event_loop();
2595
2596   lv_disconnect();
2597
2598   ignorelist_free(il_domains);
2599   il_domains = NULL;
2600   ignorelist_free(il_block_devices);
2601   il_block_devices = NULL;
2602   ignorelist_free(il_interface_devices);
2603   il_interface_devices = NULL;
2604
2605   return 0;
2606 }
2607
2608 void module_register(void) {
2609   plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
2610   plugin_register_init(PLUGIN_NAME, lv_init);
2611   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
2612 }