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