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