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