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