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