Merge pull request #2874 from elfiesmelfie/feat_virt_block_info
[collectd.git] / src / virt.c
1 /**
2  * collectd - src/virt.c
3  * Copyright (C) 2006-2008  Red Hat Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the license is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Richard W.M. Jones <rjones@redhat.com>
20  *   Przemyslaw Szczerbik <przemyslawx.szczerbik@intel.com>
21  **/
22
23 #include "collectd.h"
24
25 #include "plugin.h"
26 #include "utils/common/common.h"
27 #include "utils/ignorelist/ignorelist.h"
28 #include "utils_complain.h"
29
30 #include <libgen.h> /* for basename(3) */
31 #include <libvirt/libvirt.h>
32 #include <libvirt/virterror.h>
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xpath.h>
36 #include <libxml/xpathInternals.h>
37 #include <stdbool.h>
38
39 /* Plugin name */
40 #define PLUGIN_NAME "virt"
41
42 /* Secure strcat macro assuring null termination. Parameter (n) is the size of
43    buffer (d), allowing this macro to be safe for static and dynamic buffers */
44 #define SSTRNCAT(d, s, n)                                                      \
45   do {                                                                         \
46     size_t _l = strlen(d);                                                     \
47     sstrncpy((d) + _l, (s), (n)-_l);                                           \
48   } while (0)
49
50 #ifdef LIBVIR_CHECK_VERSION
51
52 #if LIBVIR_CHECK_VERSION(0, 9, 2)
53 #define HAVE_DOM_REASON 1
54 #endif
55
56 #if LIBVIR_CHECK_VERSION(0, 9, 5)
57 #define HAVE_BLOCK_STATS_FLAGS 1
58 #define HAVE_DOM_REASON_PAUSED_SHUTTING_DOWN 1
59 #endif
60
61 #if LIBVIR_CHECK_VERSION(0, 9, 10)
62 #define HAVE_DISK_ERR 1
63 #endif
64
65 #if LIBVIR_CHECK_VERSION(0, 9, 11)
66 #define HAVE_CPU_STATS 1
67 #define HAVE_DOM_STATE_PMSUSPENDED 1
68 #define HAVE_DOM_REASON_RUNNING_WAKEUP 1
69 #endif
70
71 /*
72   virConnectListAllDomains() appeared in 0.10.2
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 static 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   if (!block_dev) {
1722     ERROR(PLUGIN_NAME " plugin: get_block_stats NULL pointer");
1723     return -1;
1724   }
1725
1726   virDomainBlockInfo binfo;
1727   init_block_info(&binfo);
1728
1729   /* Fetching block info stats only if needed*/
1730   if (extra_stats & (ex_stats_disk_allocation | ex_stats_disk_capacity |
1731                      ex_stats_disk_physical)) {
1732     /* Block info statistics can be only fetched from devices with 'source'
1733      * defined */
1734     if (block_dev->has_source) {
1735       if (virDomainGetBlockInfo(block_dev->dom, block_dev->path, &binfo, 0) <
1736           0) {
1737         ERROR(PLUGIN_NAME " plugin: virDomainGetBlockInfo failed for path: %s",
1738               block_dev->path);
1739         return -1;
1740       }
1741     }
1742   }
1743
1744   struct lv_block_stats bstats;
1745   init_block_stats(&bstats);
1746
1747   if (lv_domain_block_stats(block_dev->dom, block_dev->path, &bstats) < 0) {
1748     ERROR(PLUGIN_NAME " plugin: lv_domain_block_stats failed");
1749     return -1;
1750   }
1751
1752   disk_block_stats_submit(&bstats, block_dev->dom, block_dev->path, &binfo);
1753   return 0;
1754 }
1755
1756 #ifdef HAVE_FS_INFO
1757
1758 #define NM_ADD_ITEM(_fun, _name, _val)                                         \
1759   do {                                                                         \
1760     ret = _fun(&notif, _name, _val);                                           \
1761     if (ret != 0) {                                                            \
1762       ERROR(PLUGIN_NAME " plugin: failed to add notification metadata");       \
1763       goto cleanup;                                                            \
1764     }                                                                          \
1765   } while (0)
1766
1767 #define NM_ADD_STR_ITEMS(_items, _size)                                        \
1768   do {                                                                         \
1769     for (size_t _i = 0; _i < _size; ++_i) {                                    \
1770       DEBUG(PLUGIN_NAME                                                        \
1771             " plugin: Adding notification metadata name=%s value=%s",          \
1772             _items[_i].name, _items[_i].value);                                \
1773       NM_ADD_ITEM(plugin_notification_meta_add_string, _items[_i].name,        \
1774                   _items[_i].value);                                           \
1775     }                                                                          \
1776   } while (0)
1777
1778 static int fs_info_notify(virDomainPtr domain, virDomainFSInfoPtr fs_info) {
1779   notification_t notif;
1780   int ret = 0;
1781
1782   /* Local struct, just for the purpose of this function. */
1783   typedef struct nm_str_item_s {
1784     const char *name;
1785     const char *value;
1786   } nm_str_item_t;
1787
1788   nm_str_item_t fs_dev_alias[fs_info->ndevAlias];
1789   nm_str_item_t fs_str_items[] = {
1790       {.name = "mountpoint", .value = fs_info->mountpoint},
1791       {.name = "name", .value = fs_info->name},
1792       {.name = "fstype", .value = fs_info->fstype}};
1793
1794   for (size_t i = 0; i < fs_info->ndevAlias; ++i) {
1795     fs_dev_alias[i].name = "devAlias";
1796     fs_dev_alias[i].value = fs_info->devAlias[i];
1797   }
1798
1799   init_notif(&notif, domain, NOTIF_OKAY, "File system information",
1800              "file_system", NULL);
1801   NM_ADD_STR_ITEMS(fs_str_items, STATIC_ARRAY_SIZE(fs_str_items));
1802   NM_ADD_ITEM(plugin_notification_meta_add_unsigned_int, "ndevAlias",
1803               fs_info->ndevAlias);
1804   NM_ADD_STR_ITEMS(fs_dev_alias, fs_info->ndevAlias);
1805
1806   plugin_dispatch_notification(&notif);
1807
1808 cleanup:
1809   if (notif.meta)
1810     plugin_notification_meta_free(notif.meta);
1811   return ret;
1812 }
1813
1814 #undef RETURN_ON_ERR
1815 #undef NM_ADD_STR_ITEMS
1816
1817 static int get_fs_info(virDomainPtr domain) {
1818   virDomainFSInfoPtr *fs_info = NULL;
1819   int ret = 0;
1820
1821   int mount_points_cnt = virDomainGetFSInfo(domain, &fs_info, 0);
1822   if (mount_points_cnt == -1) {
1823     ERROR(PLUGIN_NAME " plugin: virDomainGetFSInfo failed: %d",
1824           mount_points_cnt);
1825     return mount_points_cnt;
1826   }
1827
1828   for (int i = 0; i < mount_points_cnt; ++i) {
1829     if (fs_info_notify(domain, fs_info[i]) != 0) {
1830       ERROR(PLUGIN_NAME " plugin: failed to send file system notification "
1831                         "for mount point %s",
1832             fs_info[i]->mountpoint);
1833       ret = -1;
1834     }
1835     virDomainFSInfoFree(fs_info[i]);
1836   }
1837
1838   sfree(fs_info);
1839   return ret;
1840 }
1841
1842 #endif /* HAVE_FS_INFO */
1843
1844 #ifdef HAVE_JOB_STATS
1845 static void job_stats_submit(virDomainPtr domain, virTypedParameterPtr param) {
1846   value_t vl = {0};
1847
1848   if (param->type == VIR_TYPED_PARAM_INT)
1849     vl.derive = param->value.i;
1850   else if (param->type == VIR_TYPED_PARAM_UINT)
1851     vl.derive = param->value.ui;
1852   else if (param->type == VIR_TYPED_PARAM_LLONG)
1853     vl.derive = param->value.l;
1854   else if (param->type == VIR_TYPED_PARAM_ULLONG)
1855     vl.derive = param->value.ul;
1856   else if (param->type == VIR_TYPED_PARAM_DOUBLE)
1857     vl.derive = param->value.d;
1858   else if (param->type == VIR_TYPED_PARAM_BOOLEAN)
1859     vl.derive = param->value.b;
1860   else if (param->type == VIR_TYPED_PARAM_STRING) {
1861     submit_notif(domain, NOTIF_OKAY, param->value.s, "job_stats", param->field);
1862     return;
1863   } else {
1864     ERROR(PLUGIN_NAME " plugin: unrecognized virTypedParameterType");
1865     return;
1866   }
1867
1868   submit(domain, "job_stats", param->field, &vl, 1);
1869 }
1870
1871 static int get_job_stats(virDomainPtr domain) {
1872   int ret = 0;
1873   int job_type = 0;
1874   int nparams = 0;
1875   virTypedParameterPtr params = NULL;
1876   int flags = (extra_stats & ex_stats_job_stats_completed)
1877                   ? VIR_DOMAIN_JOB_STATS_COMPLETED
1878                   : 0;
1879
1880   ret = virDomainGetJobStats(domain, &job_type, &params, &nparams, flags);
1881   if (ret != 0) {
1882     ERROR(PLUGIN_NAME " plugin: virDomainGetJobStats failed: %d", ret);
1883     return ret;
1884   }
1885
1886   DEBUG(PLUGIN_NAME " plugin: job_type=%d nparams=%d", job_type, nparams);
1887
1888   for (int i = 0; i < nparams; ++i) {
1889     DEBUG(PLUGIN_NAME " plugin: param[%d] field=%s type=%d", i, params[i].field,
1890           params[i].type);
1891     job_stats_submit(domain, &params[i]);
1892   }
1893
1894   virTypedParamsFree(params, nparams);
1895   return ret;
1896 }
1897 #endif /* HAVE_JOB_STATS */
1898
1899 static int get_domain_metrics(domain_t *domain) {
1900   if (!domain || !domain->ptr) {
1901     ERROR(PLUGIN_NAME " plugin: get_domain_metrics: NULL pointer");
1902     return -1;
1903   }
1904
1905   virDomainInfo info;
1906   int status = virDomainGetInfo(domain->ptr, &info);
1907   if (status != 0) {
1908     ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
1909           status);
1910     return -1;
1911   }
1912
1913   if (extra_stats & ex_stats_domain_state) {
1914 #ifdef HAVE_DOM_REASON
1915     /* At this point we already know domain's state from virDomainGetInfo call,
1916      * however it doesn't provide a reason for entering particular state.
1917      * We need to get it from virDomainGetState.
1918      */
1919     GET_STATS(get_domain_state, "domain reason", domain->ptr);
1920 #endif
1921   }
1922
1923   /* Gather remaining stats only for running domains */
1924   if (info.state != VIR_DOMAIN_RUNNING)
1925     return 0;
1926
1927 #ifdef HAVE_CPU_STATS
1928   if (extra_stats & ex_stats_pcpu)
1929     get_pcpu_stats(domain->ptr);
1930 #endif
1931
1932   cpu_submit(domain, info.cpuTime);
1933
1934   memory_submit(domain->ptr, (gauge_t)info.memory * 1024);
1935
1936   GET_STATS(get_vcpu_stats, "vcpu stats", domain->ptr, info.nrVirtCpu);
1937   GET_STATS(get_memory_stats, "memory stats", domain->ptr);
1938
1939 #ifdef HAVE_PERF_STATS
1940   if (extra_stats & ex_stats_perf)
1941     GET_STATS(get_perf_events, "performance monitoring events", domain->ptr);
1942 #endif
1943
1944 #ifdef HAVE_FS_INFO
1945   if (extra_stats & ex_stats_fs_info)
1946     GET_STATS(get_fs_info, "file system info", domain->ptr);
1947 #endif
1948
1949 #ifdef HAVE_DISK_ERR
1950   if (extra_stats & ex_stats_disk_err)
1951     GET_STATS(get_disk_err, "disk errors", domain->ptr);
1952 #endif
1953
1954 #ifdef HAVE_JOB_STATS
1955   if (extra_stats &
1956       (ex_stats_job_stats_completed | ex_stats_job_stats_background))
1957     GET_STATS(get_job_stats, "job stats", domain->ptr);
1958 #endif
1959
1960   /* Update cached virDomainInfo. It has to be done after cpu_submit */
1961   memcpy(&domain->info, &info, sizeof(domain->info));
1962
1963   return 0;
1964 }
1965
1966 static int get_if_dev_stats(struct interface_device *if_dev) {
1967   virDomainInterfaceStatsStruct stats = {0};
1968   char *display_name = NULL;
1969
1970   if (!if_dev) {
1971     ERROR(PLUGIN_NAME " plugin: get_if_dev_stats: NULL pointer");
1972     return -1;
1973   }
1974
1975   switch (interface_format) {
1976   case if_address:
1977     display_name = if_dev->address;
1978     break;
1979   case if_number:
1980     display_name = if_dev->number;
1981     break;
1982   case if_name:
1983   default:
1984     display_name = if_dev->path;
1985   }
1986
1987   if (virDomainInterfaceStats(if_dev->dom, if_dev->path, &stats,
1988                               sizeof(stats)) != 0) {
1989     ERROR(PLUGIN_NAME " plugin: virDomainInterfaceStats failed");
1990     return -1;
1991   }
1992
1993   if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
1994     submit_derive2("if_octets", (derive_t)stats.rx_bytes,
1995                    (derive_t)stats.tx_bytes, if_dev->dom, display_name);
1996
1997   if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
1998     submit_derive2("if_packets", (derive_t)stats.rx_packets,
1999                    (derive_t)stats.tx_packets, if_dev->dom, display_name);
2000
2001   if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
2002     submit_derive2("if_errors", (derive_t)stats.rx_errs,
2003                    (derive_t)stats.tx_errs, if_dev->dom, display_name);
2004
2005   if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
2006     submit_derive2("if_dropped", (derive_t)stats.rx_drop,
2007                    (derive_t)stats.tx_drop, if_dev->dom, display_name);
2008   return 0;
2009 }
2010
2011 static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr con_,
2012                                      virDomainPtr dom, int event, int detail,
2013                                      __attribute__((unused)) void *opaque) {
2014   int domain_state = map_domain_event_to_state(event);
2015   int domain_reason = 0; /* 0 means UNKNOWN reason for any state */
2016 #ifdef HAVE_DOM_REASON
2017   domain_reason = map_domain_event_detail_to_reason(event, detail);
2018 #endif
2019   domain_state_submit_notif(dom, domain_state, domain_reason);
2020
2021   return 0;
2022 }
2023
2024 static int register_event_impl(void) {
2025   if (virEventRegisterDefaultImpl() < 0) {
2026     virErrorPtr err = virGetLastError();
2027     ERROR(PLUGIN_NAME
2028           " plugin: error while event implementation registering: %s",
2029           err && err->message ? err->message : "Unknown error");
2030     return -1;
2031   }
2032
2033   return 0;
2034 }
2035
2036 static void virt_notif_thread_set_active(virt_notif_thread_t *thread_data,
2037                                          const bool active) {
2038   assert(thread_data != NULL);
2039   pthread_mutex_lock(&thread_data->active_mutex);
2040   thread_data->is_active = active;
2041   pthread_mutex_unlock(&thread_data->active_mutex);
2042 }
2043
2044 static bool virt_notif_thread_is_active(virt_notif_thread_t *thread_data) {
2045   bool active = false;
2046
2047   assert(thread_data != NULL);
2048   pthread_mutex_lock(&thread_data->active_mutex);
2049   active = thread_data->is_active;
2050   pthread_mutex_unlock(&thread_data->active_mutex);
2051
2052   return active;
2053 }
2054
2055 /* worker function running default event implementation */
2056 static void *event_loop_worker(void *arg) {
2057   virt_notif_thread_t *thread_data = (virt_notif_thread_t *)arg;
2058
2059   while (virt_notif_thread_is_active(thread_data)) {
2060     if (virEventRunDefaultImpl() < 0) {
2061       virErrorPtr err = virGetLastError();
2062       ERROR(PLUGIN_NAME " plugin: failed to run event loop: %s\n",
2063             err && err->message ? err->message : "Unknown error");
2064     }
2065   }
2066
2067   return NULL;
2068 }
2069
2070 static int virt_notif_thread_init(virt_notif_thread_t *thread_data) {
2071   int ret;
2072
2073   assert(thread_data != NULL);
2074   ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
2075   if (ret != 0) {
2076     ERROR(PLUGIN_NAME " plugin: Failed to initialize mutex, err %u", ret);
2077     return ret;
2078   }
2079
2080   /**
2081    * '0' and positive integers are meaningful ID's, therefore setting
2082    * domain_event_cb_id to '-1'
2083    */
2084   thread_data->domain_event_cb_id = -1;
2085   pthread_mutex_lock(&thread_data->active_mutex);
2086   thread_data->is_active = false;
2087   pthread_mutex_unlock(&thread_data->active_mutex);
2088
2089   return 0;
2090 }
2091
2092 /* register domain event callback and start event loop thread */
2093 static int start_event_loop(virt_notif_thread_t *thread_data) {
2094   assert(thread_data != NULL);
2095   thread_data->domain_event_cb_id = virConnectDomainEventRegisterAny(
2096       conn, NULL, VIR_DOMAIN_EVENT_ID_LIFECYCLE,
2097       VIR_DOMAIN_EVENT_CALLBACK(domain_lifecycle_event_cb), NULL, NULL);
2098   if (thread_data->domain_event_cb_id == -1) {
2099     ERROR(PLUGIN_NAME " plugin: error while callback registering");
2100     return -1;
2101   }
2102
2103   virt_notif_thread_set_active(thread_data, 1);
2104   if (pthread_create(&thread_data->event_loop_tid, NULL, event_loop_worker,
2105                      thread_data)) {
2106     ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
2107     virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
2108     return -1;
2109   }
2110
2111   return 0;
2112 }
2113
2114 /* stop event loop thread and deregister callback */
2115 static void stop_event_loop(virt_notif_thread_t *thread_data) {
2116   /* stopping loop and de-registering event handler*/
2117   virt_notif_thread_set_active(thread_data, 0);
2118   if (conn != NULL && thread_data->domain_event_cb_id != -1)
2119     virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
2120
2121   if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
2122     ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
2123 }
2124
2125 static int persistent_domains_state_notification(void) {
2126   int status = 0;
2127   int n;
2128 #ifdef HAVE_LIST_ALL_DOMAINS
2129   virDomainPtr *domains = NULL;
2130   n = virConnectListAllDomains(conn, &domains,
2131                                VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
2132   if (n < 0) {
2133     VIRT_ERROR(conn, "reading list of persistent domains");
2134     status = -1;
2135   } else {
2136     DEBUG(PLUGIN_NAME " plugin: getting state of %i persistent domains", n);
2137     /* Fetch each persistent domain's state and notify it */
2138     int n_notified = n;
2139     for (int i = 0; i < n; ++i) {
2140       status = get_domain_state_notify(domains[i]);
2141       if (status != 0) {
2142         n_notified--;
2143         ERROR(PLUGIN_NAME " plugin: could not notify state of domain %s",
2144               virDomainGetName(domains[i]));
2145       }
2146       virDomainFree(domains[i]);
2147     }
2148
2149     sfree(domains);
2150     DEBUG(PLUGIN_NAME " plugin: notified state of %i persistent domains",
2151           n_notified);
2152   }
2153 #else
2154   n = virConnectNumOfDomains(conn);
2155   if (n > 0) {
2156     int *domids;
2157     /* Get list of domains. */
2158     domids = calloc(n, sizeof(*domids));
2159     if (domids == NULL) {
2160       ERROR(PLUGIN_NAME " plugin: calloc failed.");
2161       return -1;
2162     }
2163     n = virConnectListDomains(conn, domids, n);
2164     if (n < 0) {
2165       VIRT_ERROR(conn, "reading list of domains");
2166       sfree(domids);
2167       return -1;
2168     }
2169     /* Fetch info of each active domain and notify it */
2170     for (int i = 0; i < n; ++i) {
2171       virDomainInfo info;
2172       virDomainPtr dom = NULL;
2173       dom = virDomainLookupByID(conn, domids[i]);
2174       if (dom == NULL) {
2175         VIRT_ERROR(conn, "virDomainLookupByID");
2176         /* Could be that the domain went away -- ignore it anyway. */
2177         continue;
2178       }
2179       status = virDomainGetInfo(dom, &info);
2180       if (status == 0)
2181         /* virDomainGetState is not available. Submit 0, which corresponds to
2182          * unknown reason. */
2183         domain_state_submit_notif(dom, info.state, 0);
2184       else
2185         ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
2186               status);
2187
2188       virDomainFree(dom);
2189     }
2190     sfree(domids);
2191   }
2192 #endif
2193
2194   return status;
2195 }
2196
2197 static int lv_read(user_data_t *ud) {
2198   time_t t;
2199   struct lv_read_instance *inst = NULL;
2200   struct lv_read_state *state = NULL;
2201
2202   if (ud->data == NULL) {
2203     ERROR(PLUGIN_NAME " plugin: NULL userdata");
2204     return -1;
2205   }
2206
2207   inst = ud->data;
2208   state = &inst->read_state;
2209
2210   bool reconnect = conn == NULL ? true : false;
2211   /* event implementation must be registered before connection is opened */
2212   if (inst->id == 0) {
2213     if (!persistent_notification && reconnect)
2214       if (register_event_impl() != 0)
2215         return -1;
2216
2217     if (lv_connect() < 0)
2218       return -1;
2219
2220     if (!persistent_notification && reconnect && conn != NULL)
2221       if (start_event_loop(&notif_thread) != 0)
2222         return -1;
2223   }
2224
2225   time(&t);
2226
2227   /* Need to refresh domain or device lists? */
2228   if ((last_refresh == (time_t)0) ||
2229       ((interval > 0) && ((last_refresh + interval) <= t))) {
2230     if (refresh_lists(inst) != 0) {
2231       if (inst->id == 0) {
2232         if (!persistent_notification)
2233           stop_event_loop(&notif_thread);
2234         lv_disconnect();
2235       }
2236       return -1;
2237     }
2238     last_refresh = t;
2239   }
2240
2241   /* persistent domains state notifications are handled by instance 0 */
2242   if (inst->id == 0 && persistent_notification) {
2243     int status = persistent_domains_state_notification();
2244     if (status != 0)
2245       DEBUG(PLUGIN_NAME " plugin: persistent_domains_state_notifications "
2246                         "returned with status %i",
2247             status);
2248   }
2249
2250 #if COLLECT_DEBUG
2251   for (int i = 0; i < state->nr_domains; ++i)
2252     DEBUG(PLUGIN_NAME " plugin: domain %s",
2253           virDomainGetName(state->domains[i].ptr));
2254   for (int i = 0; i < state->nr_block_devices; ++i)
2255     DEBUG(PLUGIN_NAME " plugin: block device %d %s:%s", i,
2256           virDomainGetName(state->block_devices[i].dom),
2257           state->block_devices[i].path);
2258   for (int i = 0; i < state->nr_interface_devices; ++i)
2259     DEBUG(PLUGIN_NAME " plugin: interface device %d %s:%s", i,
2260           virDomainGetName(state->interface_devices[i].dom),
2261           state->interface_devices[i].path);
2262 #endif
2263
2264   /* Get domains' metrics */
2265   for (int i = 0; i < state->nr_domains; ++i) {
2266     domain_t *dom = &state->domains[i];
2267     int status = 0;
2268     if (dom->active)
2269       status = get_domain_metrics(dom);
2270 #ifdef HAVE_DOM_REASON
2271     else
2272       status = get_domain_state(dom->ptr);
2273 #endif
2274
2275     if (status != 0)
2276       ERROR(PLUGIN_NAME " plugin: failed to get metrics for domain=%s",
2277             virDomainGetName(dom->ptr));
2278   }
2279
2280   /* Get block device stats for each domain. */
2281   for (int i = 0; i < state->nr_block_devices; ++i) {
2282     int status = get_block_device_stats(&state->block_devices[i]);
2283     if (status != 0)
2284       ERROR(PLUGIN_NAME
2285             " plugin: failed to get stats for block device (%s) in domain %s",
2286             state->block_devices[i].path,
2287             virDomainGetName(state->block_devices[i].dom));
2288   }
2289
2290   /* Get interface stats for each domain. */
2291   for (int i = 0; i < state->nr_interface_devices; ++i) {
2292     int status = get_if_dev_stats(&state->interface_devices[i]);
2293     if (status != 0)
2294       ERROR(
2295           PLUGIN_NAME
2296           " plugin: failed to get interface stats for device (%s) in domain %s",
2297           state->interface_devices[i].path,
2298           virDomainGetName(state->interface_devices[i].dom));
2299   }
2300
2301   return 0;
2302 }
2303
2304 static int lv_init_instance(size_t i, plugin_read_cb callback) {
2305   struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
2306   struct lv_read_instance *inst = &(lv_ud->inst);
2307
2308   memset(lv_ud, 0, sizeof(*lv_ud));
2309
2310   snprintf(inst->tag, sizeof(inst->tag), "%s-%" PRIsz, PLUGIN_NAME, i);
2311   inst->id = i;
2312
2313   user_data_t *ud = &(lv_ud->ud);
2314   ud->data = inst;
2315   ud->free_func = NULL;
2316
2317   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
2318
2319   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
2320 }
2321
2322 static void lv_clean_read_state(struct lv_read_state *state) {
2323   free_block_devices(state);
2324   free_interface_devices(state);
2325   free_domains(state);
2326 }
2327
2328 static void lv_fini_instance(size_t i) {
2329   struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
2330   struct lv_read_state *state = &(inst->read_state);
2331
2332   lv_clean_read_state(state);
2333
2334   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
2335 }
2336
2337 static int lv_init(void) {
2338   if (virInitialize() != 0)
2339     return -1;
2340
2341   /* Init ignorelists if there was no explicit configuration */
2342   if (lv_init_ignorelists() != 0)
2343     return -1;
2344
2345   /* event implementation must be registered before connection is opened */
2346   if (!persistent_notification)
2347     if (register_event_impl() != 0)
2348       return -1;
2349
2350   if (lv_connect() != 0)
2351     return -1;
2352
2353   DEBUG(PLUGIN_NAME " plugin: starting event loop");
2354
2355   if (!persistent_notification) {
2356     virt_notif_thread_init(&notif_thread);
2357     if (start_event_loop(&notif_thread) != 0)
2358       return -1;
2359   }
2360
2361   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
2362
2363   for (int i = 0; i < nr_instances; ++i)
2364     if (lv_init_instance(i, lv_read) != 0)
2365       return -1;
2366
2367   return 0;
2368 }
2369
2370 /*
2371  * returns 0 on success and <0 on error
2372  */
2373 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
2374                              char *dom_tag) {
2375   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
2376   xmlXPathObjectPtr xpath_obj = NULL;
2377   xmlNodePtr xml_node = NULL;
2378   int ret = -1;
2379   int err;
2380
2381   err = xmlXPathRegisterNs(xpath_ctx,
2382                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
2383                            (const xmlChar *)METADATA_VM_PARTITION_URI);
2384   if (err) {
2385     ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
2386           METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
2387     goto done;
2388   }
2389
2390   snprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
2391            METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
2392   xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
2393   if (xpath_obj == NULL) {
2394     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
2395           xpath_str, dom_name);
2396     goto done;
2397   }
2398
2399   if (xpath_obj->type != XPATH_NODESET) {
2400     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
2401                       "(wanted %d) on domain %s",
2402           xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
2403     goto done;
2404   }
2405
2406   /*
2407    * from now on there is no real error, it's ok if a domain
2408    * doesn't have the metadata partition tag.
2409    */
2410   ret = 0;
2411   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
2412     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
2413                       "expected=1 on domain %s",
2414           xpath_str,
2415           (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
2416           dom_name);
2417   } else {
2418     xml_node = xpath_obj->nodesetval->nodeTab[0];
2419     sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
2420   }
2421
2422 done:
2423   /* deregister to clean up */
2424   err = xmlXPathRegisterNs(xpath_ctx,
2425                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
2426   if (err) {
2427     /* we can't really recover here */
2428     ERROR(PLUGIN_NAME
2429           " plugin: deregistration of namespace %s failed for domain %s",
2430           METADATA_VM_PARTITION_PREFIX, dom_name);
2431   }
2432   if (xpath_obj)
2433     xmlXPathFreeObject(xpath_obj);
2434
2435   return ret;
2436 }
2437
2438 static int is_known_tag(const char *dom_tag) {
2439   for (int i = 0; i < nr_instances; ++i)
2440     if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
2441       return 1;
2442   return 0;
2443 }
2444
2445 static int lv_instance_include_domain(struct lv_read_instance *inst,
2446                                       const char *dom_name,
2447                                       const char *dom_tag) {
2448   if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
2449     return 1;
2450
2451   /* instance#0 will always be there, so it is in charge of extra duties */
2452   if (inst->id == 0) {
2453     if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
2454       DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
2455                         "with unknown tag '%s'",
2456             inst->tag, dom_name, dom_tag);
2457       return 1;
2458     }
2459   }
2460
2461   return 0;
2462 }
2463
2464 static void lv_add_block_devices(struct lv_read_state *state, virDomainPtr dom,
2465                                  const char *domname,
2466                                  xmlXPathContextPtr xpath_ctx) {
2467   xmlXPathObjectPtr xpath_obj =
2468       xmlXPathEval((const xmlChar *)"/domain/devices/disk", xpath_ctx);
2469
2470   if (xpath_obj == NULL) {
2471     DEBUG(PLUGIN_NAME " plugin: no disk xpath-object found for domain %s",
2472           domname);
2473     return;
2474   }
2475
2476   if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
2477     DEBUG(PLUGIN_NAME " plugin: no disk node found for domain %s", domname);
2478     goto cleanup;
2479   }
2480
2481   xmlNodeSetPtr xml_block_devices = xpath_obj->nodesetval;
2482   for (int i = 0; i < xml_block_devices->nodeNr; ++i) {
2483     xmlNodePtr xml_device = xpath_obj->nodesetval->nodeTab[i];
2484     char *path_str = NULL;
2485     char *source_str = NULL;
2486
2487     if (!xml_device)
2488       continue;
2489
2490     /* Fetching path and source for block device */
2491     for (xmlNodePtr child = xml_device->children; child; child = child->next) {
2492       if (child->type != XML_ELEMENT_NODE)
2493         continue;
2494
2495       /* we are interested only in either "target" or "source" elements */
2496       if (xmlStrEqual(child->name, (const xmlChar *)"target"))
2497         path_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2498       else if (xmlStrEqual(child->name, (const xmlChar *)"source")) {
2499         /* name of the source is located in "dev" or "file" element (it depends
2500          * on type of source). Trying "dev" at first*/
2501         source_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2502         if (!source_str)
2503           source_str = (char *)xmlGetProp(child, (const xmlChar *)"file");
2504       }
2505       /* ignoring any other element*/
2506     }
2507
2508     /* source_str will be interpreted as a device path if blockdevice_format
2509      *  param is set to 'source'. */
2510     const char *device_path =
2511         (blockdevice_format == source) ? source_str : path_str;
2512
2513     if (!device_path) {
2514       /* no path found and we can't add block_device without it */
2515       WARNING(PLUGIN_NAME " plugin: could not generate device path for disk in "
2516                           "domain %s - disk device will be ignored in reports",
2517               domname);
2518       goto cont;
2519     }
2520
2521     if (ignore_device_match(il_block_devices, domname, device_path) == 0) {
2522       /* we only have to store information whether 'source' exists or not */
2523       bool has_source = (source_str != NULL) ? true : false;
2524
2525       add_block_device(state, dom, device_path, has_source);
2526     }
2527
2528   cont:
2529     if (path_str)
2530       xmlFree(path_str);
2531
2532     if (source_str)
2533       xmlFree(source_str);
2534   }
2535
2536 cleanup:
2537   xmlXPathFreeObject(xpath_obj);
2538 }
2539
2540 static void lv_add_network_interfaces(struct lv_read_state *state,
2541                                       virDomainPtr dom, const char *domname,
2542                                       xmlXPathContextPtr xpath_ctx) {
2543   xmlXPathObjectPtr xpath_obj = xmlXPathEval(
2544       (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
2545
2546   if (xpath_obj == NULL)
2547     return;
2548
2549   if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
2550     xmlXPathFreeObject(xpath_obj);
2551     return;
2552   }
2553
2554   xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
2555
2556   for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
2557     char *path = NULL;
2558     char *address = NULL;
2559
2560     xmlNodePtr xml_interface = xml_interfaces->nodeTab[j];
2561     if (!xml_interface)
2562       continue;
2563
2564     for (xmlNodePtr child = xml_interface->children; child;
2565          child = child->next) {
2566       if (child->type != XML_ELEMENT_NODE)
2567         continue;
2568
2569       if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
2570         path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2571         if (!path)
2572           continue;
2573       } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
2574         address = (char *)xmlGetProp(child, (const xmlChar *)"address");
2575         if (!address)
2576           continue;
2577       }
2578     }
2579
2580     if ((ignore_device_match(il_interface_devices, domname, path) == 0 &&
2581          ignore_device_match(il_interface_devices, domname, address) == 0)) {
2582       add_interface_device(state, dom, path, address, j + 1);
2583     }
2584
2585     if (path)
2586       xmlFree(path);
2587     if (address)
2588       xmlFree(address);
2589   }
2590   xmlXPathFreeObject(xpath_obj);
2591 }
2592
2593 static int refresh_lists(struct lv_read_instance *inst) {
2594   struct lv_read_state *state = &inst->read_state;
2595   int n;
2596
2597 #ifndef HAVE_LIST_ALL_DOMAINS
2598   n = virConnectNumOfDomains(conn);
2599   if (n < 0) {
2600     VIRT_ERROR(conn, "reading number of domains");
2601     return -1;
2602   }
2603 #endif
2604
2605   lv_clean_read_state(state);
2606
2607 #ifndef HAVE_LIST_ALL_DOMAINS
2608   if (n == 0)
2609     goto end;
2610 #endif
2611
2612 #ifdef HAVE_LIST_ALL_DOMAINS
2613   virDomainPtr *domains, *domains_inactive;
2614   int m = virConnectListAllDomains(conn, &domains_inactive,
2615                                    VIR_CONNECT_LIST_DOMAINS_INACTIVE);
2616   n = virConnectListAllDomains(conn, &domains, VIR_CONNECT_LIST_DOMAINS_ACTIVE);
2617 #else
2618   /* Get list of domains. */
2619   int *domids = calloc(n, sizeof(*domids));
2620   if (domids == NULL) {
2621     ERROR(PLUGIN_NAME " plugin: calloc failed.");
2622     return -1;
2623   }
2624
2625   n = virConnectListDomains(conn, domids, n);
2626 #endif
2627
2628   if (n < 0) {
2629     VIRT_ERROR(conn, "reading list of domains");
2630 #ifndef HAVE_LIST_ALL_DOMAINS
2631     sfree(domids);
2632 #else
2633     for (int i = 0; i < m; ++i)
2634       virDomainFree(domains_inactive[i]);
2635     sfree(domains_inactive);
2636 #endif
2637     return -1;
2638   }
2639
2640 #ifdef HAVE_LIST_ALL_DOMAINS
2641   for (int i = 0; i < m; ++i)
2642     if (add_domain(state, domains_inactive[i], 0) < 0) {
2643       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2644       virDomainFree(domains_inactive[i]);
2645       domains_inactive[i] = NULL;
2646       continue;
2647     }
2648 #endif
2649
2650   /* Fetch each domain and add it to the list, unless ignore. */
2651   for (int i = 0; i < n; ++i) {
2652
2653 #ifdef HAVE_LIST_ALL_DOMAINS
2654     virDomainPtr dom = domains[i];
2655 #else
2656     virDomainPtr dom = virDomainLookupByID(conn, domids[i]);
2657     if (dom == NULL) {
2658       VIRT_ERROR(conn, "virDomainLookupByID");
2659       /* Could be that the domain went away -- ignore it anyway. */
2660       continue;
2661     }
2662 #endif
2663
2664     if (add_domain(state, dom, 1) < 0) {
2665       /*
2666        * When domain is already tracked, then there is
2667        * no problem with memory handling (will be freed
2668        * with the rest of domains cached data)
2669        * But in case of error like this (error occurred
2670        * before adding domain to track) we have to take
2671        * care it ourselves and call virDomainFree
2672        */
2673       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2674       virDomainFree(dom);
2675       continue;
2676     }
2677
2678     const char *domname = virDomainGetName(dom);
2679     if (domname == NULL) {
2680       VIRT_ERROR(conn, "virDomainGetName");
2681       continue;
2682     }
2683
2684     virDomainInfo info;
2685     int status = virDomainGetInfo(dom, &info);
2686     if (status != 0) {
2687       ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
2688             status);
2689       continue;
2690     }
2691
2692     if (info.state != VIR_DOMAIN_RUNNING) {
2693       DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", domname);
2694       continue;
2695     }
2696
2697     if (ignorelist_match(il_domains, domname) != 0)
2698       continue;
2699
2700     /* Get a list of devices for this domain. */
2701     xmlDocPtr xml_doc = NULL;
2702     xmlXPathContextPtr xpath_ctx = NULL;
2703
2704     char *xml = virDomainGetXMLDesc(dom, 0);
2705     if (!xml) {
2706       VIRT_ERROR(conn, "virDomainGetXMLDesc");
2707       goto cont;
2708     }
2709
2710     /* Yuck, XML.  Parse out the devices. */
2711     xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
2712     if (xml_doc == NULL) {
2713       VIRT_ERROR(conn, "xmlReadDoc");
2714       goto cont;
2715     }
2716
2717     xpath_ctx = xmlXPathNewContext(xml_doc);
2718
2719     char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
2720     if (lv_domain_get_tag(xpath_ctx, domname, tag) < 0) {
2721       ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
2722       goto cont;
2723     }
2724
2725     if (!lv_instance_include_domain(inst, domname, tag))
2726       goto cont;
2727
2728     /* Block devices. */
2729     if (report_block_devices)
2730       lv_add_block_devices(state, dom, domname, xpath_ctx);
2731
2732     /* Network interfaces. */
2733     if (report_network_interfaces)
2734       lv_add_network_interfaces(state, dom, domname, xpath_ctx);
2735
2736   cont:
2737     if (xpath_ctx)
2738       xmlXPathFreeContext(xpath_ctx);
2739     if (xml_doc)
2740       xmlFreeDoc(xml_doc);
2741     sfree(xml);
2742   }
2743
2744 #ifdef HAVE_LIST_ALL_DOMAINS
2745   /* NOTE: domains_active and domains_inactive data will be cleared during
2746      refresh of all domains (inside lv_clean_read_state function) so we need
2747      to free here only allocated arrays */
2748   sfree(domains);
2749   sfree(domains_inactive);
2750 #else
2751   sfree(domids);
2752
2753 end:
2754 #endif
2755
2756   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
2757                     " domains=%i block_devices=%i iface_devices=%i",
2758         inst->tag, state->nr_domains, state->nr_block_devices,
2759         state->nr_interface_devices);
2760
2761   return 0;
2762 }
2763
2764 static void free_domains(struct lv_read_state *state) {
2765   if (state->domains) {
2766     for (int i = 0; i < state->nr_domains; ++i)
2767       virDomainFree(state->domains[i].ptr);
2768     sfree(state->domains);
2769   }
2770   state->domains = NULL;
2771   state->nr_domains = 0;
2772 }
2773
2774 static int add_domain(struct lv_read_state *state, virDomainPtr dom,
2775                       bool active) {
2776
2777   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
2778
2779   domain_t *new_ptr = realloc(state->domains, new_size);
2780   if (new_ptr == NULL)
2781     return -1;
2782
2783   state->domains = new_ptr;
2784   state->domains[state->nr_domains].ptr = dom;
2785   state->domains[state->nr_domains].active = active;
2786   memset(&state->domains[state->nr_domains].info, 0,
2787          sizeof(state->domains[state->nr_domains].info));
2788
2789   return state->nr_domains++;
2790 }
2791
2792 static void free_block_devices(struct lv_read_state *state) {
2793   if (state->block_devices) {
2794     for (int i = 0; i < state->nr_block_devices; ++i)
2795       sfree(state->block_devices[i].path);
2796     sfree(state->block_devices);
2797   }
2798   state->block_devices = NULL;
2799   state->nr_block_devices = 0;
2800 }
2801
2802 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
2803                             const char *path, bool has_source) {
2804
2805   char *path_copy = strdup(path);
2806   if (!path_copy)
2807     return -1;
2808
2809   int new_size =
2810       sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
2811
2812   struct block_device *new_ptr = realloc(state->block_devices, new_size);
2813   if (new_ptr == NULL) {
2814     sfree(path_copy);
2815     return -1;
2816   }
2817   state->block_devices = new_ptr;
2818   state->block_devices[state->nr_block_devices].dom = dom;
2819   state->block_devices[state->nr_block_devices].path = path_copy;
2820   state->block_devices[state->nr_block_devices].has_source = has_source;
2821   return state->nr_block_devices++;
2822 }
2823
2824 static void free_interface_devices(struct lv_read_state *state) {
2825   if (state->interface_devices) {
2826     for (int i = 0; i < state->nr_interface_devices; ++i) {
2827       sfree(state->interface_devices[i].path);
2828       sfree(state->interface_devices[i].address);
2829       sfree(state->interface_devices[i].number);
2830     }
2831     sfree(state->interface_devices);
2832   }
2833   state->interface_devices = NULL;
2834   state->nr_interface_devices = 0;
2835 }
2836
2837 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
2838                                 const char *path, const char *address,
2839                                 unsigned int number) {
2840
2841   if ((path == NULL) || (address == NULL))
2842     return EINVAL;
2843
2844   char *path_copy = strdup(path);
2845   if (!path_copy)
2846     return -1;
2847
2848   char *address_copy = strdup(address);
2849   if (!address_copy) {
2850     sfree(path_copy);
2851     return -1;
2852   }
2853
2854   char number_string[21];
2855   snprintf(number_string, sizeof(number_string), "interface-%u", number);
2856   char *number_copy = strdup(number_string);
2857   if (!number_copy) {
2858     sfree(path_copy);
2859     sfree(address_copy);
2860     return -1;
2861   }
2862
2863   int new_size =
2864       sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
2865
2866   struct interface_device *new_ptr =
2867       realloc(state->interface_devices, new_size);
2868   if (new_ptr == NULL) {
2869     sfree(path_copy);
2870     sfree(address_copy);
2871     sfree(number_copy);
2872     return -1;
2873   }
2874
2875   state->interface_devices = new_ptr;
2876   state->interface_devices[state->nr_interface_devices].dom = dom;
2877   state->interface_devices[state->nr_interface_devices].path = path_copy;
2878   state->interface_devices[state->nr_interface_devices].address = address_copy;
2879   state->interface_devices[state->nr_interface_devices].number = number_copy;
2880   return state->nr_interface_devices++;
2881 }
2882
2883 static int ignore_device_match(ignorelist_t *il, const char *domname,
2884                                const char *devpath) {
2885   if ((domname == NULL) || (devpath == NULL))
2886     return 0;
2887
2888   size_t n = strlen(domname) + strlen(devpath) + 2;
2889   char *name = malloc(n);
2890   if (name == NULL) {
2891     ERROR(PLUGIN_NAME " plugin: malloc failed.");
2892     return 0;
2893   }
2894   snprintf(name, n, "%s:%s", domname, devpath);
2895   int r = ignorelist_match(il, name);
2896   sfree(name);
2897   return r;
2898 }
2899
2900 static int lv_shutdown(void) {
2901   for (int i = 0; i < nr_instances; ++i) {
2902     lv_fini_instance(i);
2903   }
2904
2905   DEBUG(PLUGIN_NAME " plugin: stopping event loop");
2906
2907   if (!persistent_notification)
2908     stop_event_loop(&notif_thread);
2909
2910   lv_disconnect();
2911
2912   ignorelist_free(il_domains);
2913   il_domains = NULL;
2914   ignorelist_free(il_block_devices);
2915   il_block_devices = NULL;
2916   ignorelist_free(il_interface_devices);
2917   il_interface_devices = NULL;
2918
2919   return 0;
2920 }
2921
2922 void module_register(void) {
2923   plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
2924   plugin_register_init(PLUGIN_NAME, lv_init);
2925   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
2926 }