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