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