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