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