Merge pull request #3116 from rpv-tomsk/collectd-master
[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   DEBUG(PLUGIN_NAME " plugin: starting event loop");
2104
2105   virt_notif_thread_set_active(thread_data, 1);
2106   if (pthread_create(&thread_data->event_loop_tid, NULL, event_loop_worker,
2107                      thread_data)) {
2108     ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
2109     virt_notif_thread_set_active(thread_data, 0);
2110     virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
2111     thread_data->domain_event_cb_id = -1;
2112     return -1;
2113   }
2114
2115   return 0;
2116 }
2117
2118 /* stop event loop thread and deregister callback */
2119 static void stop_event_loop(virt_notif_thread_t *thread_data) {
2120
2121   DEBUG(PLUGIN_NAME " plugin: stopping event loop");
2122
2123   /* Stopping loop */
2124   if (virt_notif_thread_is_active(thread_data)) {
2125     virt_notif_thread_set_active(thread_data, 0);
2126     if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
2127       ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
2128   }
2129
2130   /* ... and de-registering event handler */
2131   if (conn != NULL && thread_data->domain_event_cb_id != -1) {
2132     virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
2133     thread_data->domain_event_cb_id = -1;
2134   }
2135 }
2136
2137 static int persistent_domains_state_notification(void) {
2138   int status = 0;
2139   int n;
2140 #ifdef HAVE_LIST_ALL_DOMAINS
2141   virDomainPtr *domains = NULL;
2142   n = virConnectListAllDomains(conn, &domains,
2143                                VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
2144   if (n < 0) {
2145     VIRT_ERROR(conn, "reading list of persistent domains");
2146     status = -1;
2147   } else {
2148     DEBUG(PLUGIN_NAME " plugin: getting state of %i persistent domains", n);
2149     /* Fetch each persistent domain's state and notify it */
2150     int n_notified = n;
2151     for (int i = 0; i < n; ++i) {
2152       status = get_domain_state_notify(domains[i]);
2153       if (status != 0) {
2154         n_notified--;
2155         ERROR(PLUGIN_NAME " plugin: could not notify state of domain %s",
2156               virDomainGetName(domains[i]));
2157       }
2158       virDomainFree(domains[i]);
2159     }
2160
2161     sfree(domains);
2162     DEBUG(PLUGIN_NAME " plugin: notified state of %i persistent domains",
2163           n_notified);
2164   }
2165 #else
2166   n = virConnectNumOfDomains(conn);
2167   if (n > 0) {
2168     int *domids;
2169     /* Get list of domains. */
2170     domids = calloc(n, sizeof(*domids));
2171     if (domids == NULL) {
2172       ERROR(PLUGIN_NAME " plugin: calloc failed.");
2173       return -1;
2174     }
2175     n = virConnectListDomains(conn, domids, n);
2176     if (n < 0) {
2177       VIRT_ERROR(conn, "reading list of domains");
2178       sfree(domids);
2179       return -1;
2180     }
2181     /* Fetch info of each active domain and notify it */
2182     for (int i = 0; i < n; ++i) {
2183       virDomainInfo info;
2184       virDomainPtr dom = NULL;
2185       dom = virDomainLookupByID(conn, domids[i]);
2186       if (dom == NULL) {
2187         VIRT_ERROR(conn, "virDomainLookupByID");
2188         /* Could be that the domain went away -- ignore it anyway. */
2189         continue;
2190       }
2191       status = virDomainGetInfo(dom, &info);
2192       if (status == 0)
2193         /* virDomainGetState is not available. Submit 0, which corresponds to
2194          * unknown reason. */
2195         domain_state_submit_notif(dom, info.state, 0);
2196       else
2197         ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
2198               status);
2199
2200       virDomainFree(dom);
2201     }
2202     sfree(domids);
2203   }
2204 #endif
2205
2206   return status;
2207 }
2208
2209 static int lv_read(user_data_t *ud) {
2210   time_t t;
2211   struct lv_read_instance *inst = NULL;
2212   struct lv_read_state *state = NULL;
2213
2214   if (ud->data == NULL) {
2215     ERROR(PLUGIN_NAME " plugin: NULL userdata");
2216     return -1;
2217   }
2218
2219   inst = ud->data;
2220   state = &inst->read_state;
2221
2222   bool reconnect = conn == NULL ? true : false;
2223   /* event implementation must be registered before connection is opened */
2224   if (inst->id == 0) {
2225     if (!persistent_notification && reconnect)
2226       if (register_event_impl() != 0)
2227         return -1;
2228
2229     if (lv_connect() < 0)
2230       return -1;
2231
2232     if (!persistent_notification && reconnect && conn != NULL)
2233       if (start_event_loop(&notif_thread) != 0)
2234         return -1;
2235   }
2236
2237   time(&t);
2238
2239   /* Need to refresh domain or device lists? */
2240   if ((last_refresh == (time_t)0) ||
2241       ((interval > 0) && ((last_refresh + interval) <= t))) {
2242     if (refresh_lists(inst) != 0) {
2243       if (inst->id == 0) {
2244         if (!persistent_notification)
2245           stop_event_loop(&notif_thread);
2246         lv_disconnect();
2247       }
2248       return -1;
2249     }
2250     last_refresh = t;
2251   }
2252
2253   /* persistent domains state notifications are handled by instance 0 */
2254   if (inst->id == 0 && persistent_notification) {
2255     int status = persistent_domains_state_notification();
2256     if (status != 0)
2257       DEBUG(PLUGIN_NAME " plugin: persistent_domains_state_notifications "
2258                         "returned with status %i",
2259             status);
2260   }
2261
2262 #if COLLECT_DEBUG
2263   for (int i = 0; i < state->nr_domains; ++i)
2264     DEBUG(PLUGIN_NAME " plugin: domain %s",
2265           virDomainGetName(state->domains[i].ptr));
2266   for (int i = 0; i < state->nr_block_devices; ++i)
2267     DEBUG(PLUGIN_NAME " plugin: block device %d %s:%s", i,
2268           virDomainGetName(state->block_devices[i].dom),
2269           state->block_devices[i].path);
2270   for (int i = 0; i < state->nr_interface_devices; ++i)
2271     DEBUG(PLUGIN_NAME " plugin: interface device %d %s:%s", i,
2272           virDomainGetName(state->interface_devices[i].dom),
2273           state->interface_devices[i].path);
2274 #endif
2275
2276   /* Get domains' metrics */
2277   for (int i = 0; i < state->nr_domains; ++i) {
2278     domain_t *dom = &state->domains[i];
2279     int status = 0;
2280     if (dom->active)
2281       status = get_domain_metrics(dom);
2282 #ifdef HAVE_DOM_REASON
2283     else
2284       status = get_domain_state(dom->ptr);
2285 #endif
2286
2287     if (status != 0)
2288       ERROR(PLUGIN_NAME " plugin: failed to get metrics for domain=%s",
2289             virDomainGetName(dom->ptr));
2290   }
2291
2292   /* Get block device stats for each domain. */
2293   for (int i = 0; i < state->nr_block_devices; ++i) {
2294     int status = get_block_device_stats(&state->block_devices[i]);
2295     if (status != 0)
2296       ERROR(PLUGIN_NAME
2297             " plugin: failed to get stats for block device (%s) in domain %s",
2298             state->block_devices[i].path,
2299             virDomainGetName(state->block_devices[i].dom));
2300   }
2301
2302   /* Get interface stats for each domain. */
2303   for (int i = 0; i < state->nr_interface_devices; ++i) {
2304     int status = get_if_dev_stats(&state->interface_devices[i]);
2305     if (status != 0)
2306       ERROR(
2307           PLUGIN_NAME
2308           " plugin: failed to get interface stats for device (%s) in domain %s",
2309           state->interface_devices[i].path,
2310           virDomainGetName(state->interface_devices[i].dom));
2311   }
2312
2313   return 0;
2314 }
2315
2316 static int lv_init_instance(size_t i, plugin_read_cb callback) {
2317   struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
2318   struct lv_read_instance *inst = &(lv_ud->inst);
2319
2320   memset(lv_ud, 0, sizeof(*lv_ud));
2321
2322   snprintf(inst->tag, sizeof(inst->tag), "%s-%" PRIsz, PLUGIN_NAME, i);
2323   inst->id = i;
2324
2325   user_data_t *ud = &(lv_ud->ud);
2326   ud->data = inst;
2327   ud->free_func = NULL;
2328
2329   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
2330
2331   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
2332 }
2333
2334 static void lv_clean_read_state(struct lv_read_state *state) {
2335   free_block_devices(state);
2336   free_interface_devices(state);
2337   free_domains(state);
2338 }
2339
2340 static void lv_fini_instance(size_t i) {
2341   struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
2342   struct lv_read_state *state = &(inst->read_state);
2343
2344   lv_clean_read_state(state);
2345
2346   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
2347 }
2348
2349 static int lv_init(void) {
2350   if (virInitialize() != 0)
2351     return -1;
2352
2353   /* Init ignorelists if there was no explicit configuration */
2354   if (lv_init_ignorelists() != 0)
2355     return -1;
2356
2357   /* event implementation must be registered before connection is opened */
2358   if (!persistent_notification)
2359     if (register_event_impl() != 0)
2360       return -1;
2361
2362   if (lv_connect() != 0)
2363     return -1;
2364
2365   if (!persistent_notification) {
2366     virt_notif_thread_init(&notif_thread);
2367     if (start_event_loop(&notif_thread) != 0)
2368       return -1;
2369   }
2370
2371   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
2372
2373   for (int i = 0; i < nr_instances; ++i)
2374     if (lv_init_instance(i, lv_read) != 0)
2375       return -1;
2376
2377   return 0;
2378 }
2379
2380 /*
2381  * returns 0 on success and <0 on error
2382  */
2383 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
2384                              char *dom_tag) {
2385   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
2386   xmlXPathObjectPtr xpath_obj = NULL;
2387   xmlNodePtr xml_node = NULL;
2388   int ret = -1;
2389   int err;
2390
2391   err = xmlXPathRegisterNs(xpath_ctx,
2392                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
2393                            (const xmlChar *)METADATA_VM_PARTITION_URI);
2394   if (err) {
2395     ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
2396           METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
2397     goto done;
2398   }
2399
2400   snprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
2401            METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
2402   xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
2403   if (xpath_obj == NULL) {
2404     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
2405           xpath_str, dom_name);
2406     goto done;
2407   }
2408
2409   if (xpath_obj->type != XPATH_NODESET) {
2410     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
2411                       "(wanted %d) on domain %s",
2412           xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
2413     goto done;
2414   }
2415
2416   /*
2417    * from now on there is no real error, it's ok if a domain
2418    * doesn't have the metadata partition tag.
2419    */
2420   ret = 0;
2421   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
2422     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
2423                       "expected=1 on domain %s",
2424           xpath_str,
2425           (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
2426           dom_name);
2427   } else {
2428     xml_node = xpath_obj->nodesetval->nodeTab[0];
2429     sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
2430   }
2431
2432 done:
2433   /* deregister to clean up */
2434   err = xmlXPathRegisterNs(xpath_ctx,
2435                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
2436   if (err) {
2437     /* we can't really recover here */
2438     ERROR(PLUGIN_NAME
2439           " plugin: deregistration of namespace %s failed for domain %s",
2440           METADATA_VM_PARTITION_PREFIX, dom_name);
2441   }
2442   if (xpath_obj)
2443     xmlXPathFreeObject(xpath_obj);
2444
2445   return ret;
2446 }
2447
2448 static int is_known_tag(const char *dom_tag) {
2449   for (int i = 0; i < nr_instances; ++i)
2450     if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
2451       return 1;
2452   return 0;
2453 }
2454
2455 static int lv_instance_include_domain(struct lv_read_instance *inst,
2456                                       const char *dom_name,
2457                                       const char *dom_tag) {
2458   if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
2459     return 1;
2460
2461   /* instance#0 will always be there, so it is in charge of extra duties */
2462   if (inst->id == 0) {
2463     if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
2464       DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
2465                         "with unknown tag '%s'",
2466             inst->tag, dom_name, dom_tag);
2467       return 1;
2468     }
2469   }
2470
2471   return 0;
2472 }
2473
2474 static void lv_add_block_devices(struct lv_read_state *state, virDomainPtr dom,
2475                                  const char *domname,
2476                                  xmlXPathContextPtr xpath_ctx) {
2477   xmlXPathObjectPtr xpath_obj =
2478       xmlXPathEval((const xmlChar *)"/domain/devices/disk", xpath_ctx);
2479
2480   if (xpath_obj == NULL) {
2481     DEBUG(PLUGIN_NAME " plugin: no disk xpath-object found for domain %s",
2482           domname);
2483     return;
2484   }
2485
2486   if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
2487     DEBUG(PLUGIN_NAME " plugin: no disk node found for domain %s", domname);
2488     goto cleanup;
2489   }
2490
2491   xmlNodeSetPtr xml_block_devices = xpath_obj->nodesetval;
2492   for (int i = 0; i < xml_block_devices->nodeNr; ++i) {
2493     xmlNodePtr xml_device = xpath_obj->nodesetval->nodeTab[i];
2494     char *path_str = NULL;
2495     char *source_str = NULL;
2496
2497     if (!xml_device)
2498       continue;
2499
2500     /* Fetching path and source for block device */
2501     for (xmlNodePtr child = xml_device->children; child; child = child->next) {
2502       if (child->type != XML_ELEMENT_NODE)
2503         continue;
2504
2505       /* we are interested only in either "target" or "source" elements */
2506       if (xmlStrEqual(child->name, (const xmlChar *)"target"))
2507         path_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2508       else if (xmlStrEqual(child->name, (const xmlChar *)"source")) {
2509         /* name of the source is located in "dev" or "file" element (it depends
2510          * on type of source). Trying "dev" at first*/
2511         source_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2512         if (!source_str)
2513           source_str = (char *)xmlGetProp(child, (const xmlChar *)"file");
2514       }
2515       /* ignoring any other element*/
2516     }
2517
2518     /* source_str will be interpreted as a device path if blockdevice_format
2519      *  param is set to 'source'. */
2520     const char *device_path =
2521         (blockdevice_format == source) ? source_str : path_str;
2522
2523     if (!device_path) {
2524       /* no path found and we can't add block_device without it */
2525       WARNING(PLUGIN_NAME " plugin: could not generate device path for disk in "
2526                           "domain %s - disk device will be ignored in reports",
2527               domname);
2528       goto cont;
2529     }
2530
2531     if (ignore_device_match(il_block_devices, domname, device_path) == 0) {
2532       /* we only have to store information whether 'source' exists or not */
2533       bool has_source = (source_str != NULL) ? true : false;
2534
2535       add_block_device(state, dom, device_path, has_source);
2536     }
2537
2538   cont:
2539     if (path_str)
2540       xmlFree(path_str);
2541
2542     if (source_str)
2543       xmlFree(source_str);
2544   }
2545
2546 cleanup:
2547   xmlXPathFreeObject(xpath_obj);
2548 }
2549
2550 static void lv_add_network_interfaces(struct lv_read_state *state,
2551                                       virDomainPtr dom, const char *domname,
2552                                       xmlXPathContextPtr xpath_ctx) {
2553   xmlXPathObjectPtr xpath_obj = xmlXPathEval(
2554       (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
2555
2556   if (xpath_obj == NULL)
2557     return;
2558
2559   if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
2560     xmlXPathFreeObject(xpath_obj);
2561     return;
2562   }
2563
2564   xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
2565
2566   for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
2567     char *path = NULL;
2568     char *address = NULL;
2569
2570     xmlNodePtr xml_interface = xml_interfaces->nodeTab[j];
2571     if (!xml_interface)
2572       continue;
2573
2574     for (xmlNodePtr child = xml_interface->children; child;
2575          child = child->next) {
2576       if (child->type != XML_ELEMENT_NODE)
2577         continue;
2578
2579       if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
2580         path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
2581         if (!path)
2582           continue;
2583       } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
2584         address = (char *)xmlGetProp(child, (const xmlChar *)"address");
2585         if (!address)
2586           continue;
2587       }
2588     }
2589
2590     if ((ignore_device_match(il_interface_devices, domname, path) == 0 &&
2591          ignore_device_match(il_interface_devices, domname, address) == 0)) {
2592       add_interface_device(state, dom, path, address, j + 1);
2593     }
2594
2595     if (path)
2596       xmlFree(path);
2597     if (address)
2598       xmlFree(address);
2599   }
2600   xmlXPathFreeObject(xpath_obj);
2601 }
2602
2603 static int refresh_lists(struct lv_read_instance *inst) {
2604   struct lv_read_state *state = &inst->read_state;
2605   int n;
2606
2607 #ifndef HAVE_LIST_ALL_DOMAINS
2608   n = virConnectNumOfDomains(conn);
2609   if (n < 0) {
2610     VIRT_ERROR(conn, "reading number of domains");
2611     return -1;
2612   }
2613 #endif
2614
2615   lv_clean_read_state(state);
2616
2617 #ifndef HAVE_LIST_ALL_DOMAINS
2618   if (n == 0)
2619     goto end;
2620 #endif
2621
2622 #ifdef HAVE_LIST_ALL_DOMAINS
2623   virDomainPtr *domains, *domains_inactive;
2624   int m = virConnectListAllDomains(conn, &domains_inactive,
2625                                    VIR_CONNECT_LIST_DOMAINS_INACTIVE);
2626   n = virConnectListAllDomains(conn, &domains, VIR_CONNECT_LIST_DOMAINS_ACTIVE);
2627 #else
2628   /* Get list of domains. */
2629   int *domids = calloc(n, sizeof(*domids));
2630   if (domids == NULL) {
2631     ERROR(PLUGIN_NAME " plugin: calloc failed.");
2632     return -1;
2633   }
2634
2635   n = virConnectListDomains(conn, domids, n);
2636 #endif
2637
2638   if (n < 0) {
2639     VIRT_ERROR(conn, "reading list of domains");
2640 #ifndef HAVE_LIST_ALL_DOMAINS
2641     sfree(domids);
2642 #else
2643     for (int i = 0; i < m; ++i)
2644       virDomainFree(domains_inactive[i]);
2645     sfree(domains_inactive);
2646 #endif
2647     return -1;
2648   }
2649
2650 #ifdef HAVE_LIST_ALL_DOMAINS
2651   for (int i = 0; i < m; ++i)
2652     if (add_domain(state, domains_inactive[i], 0) < 0) {
2653       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2654       virDomainFree(domains_inactive[i]);
2655       domains_inactive[i] = NULL;
2656       continue;
2657     }
2658 #endif
2659
2660   /* Fetch each domain and add it to the list, unless ignore. */
2661   for (int i = 0; i < n; ++i) {
2662
2663 #ifdef HAVE_LIST_ALL_DOMAINS
2664     virDomainPtr dom = domains[i];
2665 #else
2666     virDomainPtr dom = virDomainLookupByID(conn, domids[i]);
2667     if (dom == NULL) {
2668       VIRT_ERROR(conn, "virDomainLookupByID");
2669       /* Could be that the domain went away -- ignore it anyway. */
2670       continue;
2671     }
2672 #endif
2673
2674     if (add_domain(state, dom, 1) < 0) {
2675       /*
2676        * When domain is already tracked, then there is
2677        * no problem with memory handling (will be freed
2678        * with the rest of domains cached data)
2679        * But in case of error like this (error occurred
2680        * before adding domain to track) we have to take
2681        * care it ourselves and call virDomainFree
2682        */
2683       ERROR(PLUGIN_NAME " plugin: malloc failed.");
2684       virDomainFree(dom);
2685       continue;
2686     }
2687
2688     const char *domname = virDomainGetName(dom);
2689     if (domname == NULL) {
2690       VIRT_ERROR(conn, "virDomainGetName");
2691       continue;
2692     }
2693
2694     virDomainInfo info;
2695     int status = virDomainGetInfo(dom, &info);
2696     if (status != 0) {
2697       ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
2698             status);
2699       continue;
2700     }
2701
2702     if (info.state != VIR_DOMAIN_RUNNING) {
2703       DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", domname);
2704       continue;
2705     }
2706
2707     if (ignorelist_match(il_domains, domname) != 0)
2708       continue;
2709
2710     /* Get a list of devices for this domain. */
2711     xmlDocPtr xml_doc = NULL;
2712     xmlXPathContextPtr xpath_ctx = NULL;
2713
2714     char *xml = virDomainGetXMLDesc(dom, 0);
2715     if (!xml) {
2716       VIRT_ERROR(conn, "virDomainGetXMLDesc");
2717       goto cont;
2718     }
2719
2720     /* Yuck, XML.  Parse out the devices. */
2721     xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
2722     if (xml_doc == NULL) {
2723       VIRT_ERROR(conn, "xmlReadDoc");
2724       goto cont;
2725     }
2726
2727     xpath_ctx = xmlXPathNewContext(xml_doc);
2728
2729     char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
2730     if (lv_domain_get_tag(xpath_ctx, domname, tag) < 0) {
2731       ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
2732       goto cont;
2733     }
2734
2735     if (!lv_instance_include_domain(inst, domname, tag))
2736       goto cont;
2737
2738     /* Block devices. */
2739     if (report_block_devices)
2740       lv_add_block_devices(state, dom, domname, xpath_ctx);
2741
2742     /* Network interfaces. */
2743     if (report_network_interfaces)
2744       lv_add_network_interfaces(state, dom, domname, xpath_ctx);
2745
2746   cont:
2747     if (xpath_ctx)
2748       xmlXPathFreeContext(xpath_ctx);
2749     if (xml_doc)
2750       xmlFreeDoc(xml_doc);
2751     sfree(xml);
2752   }
2753
2754 #ifdef HAVE_LIST_ALL_DOMAINS
2755   /* NOTE: domains_active and domains_inactive data will be cleared during
2756      refresh of all domains (inside lv_clean_read_state function) so we need
2757      to free here only allocated arrays */
2758   sfree(domains);
2759   sfree(domains_inactive);
2760 #else
2761   sfree(domids);
2762
2763 end:
2764 #endif
2765
2766   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
2767                     " domains=%i block_devices=%i iface_devices=%i",
2768         inst->tag, state->nr_domains, state->nr_block_devices,
2769         state->nr_interface_devices);
2770
2771   return 0;
2772 }
2773
2774 static void free_domains(struct lv_read_state *state) {
2775   if (state->domains) {
2776     for (int i = 0; i < state->nr_domains; ++i)
2777       virDomainFree(state->domains[i].ptr);
2778     sfree(state->domains);
2779   }
2780   state->domains = NULL;
2781   state->nr_domains = 0;
2782 }
2783
2784 static int add_domain(struct lv_read_state *state, virDomainPtr dom,
2785                       bool active) {
2786
2787   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
2788
2789   domain_t *new_ptr = realloc(state->domains, new_size);
2790   if (new_ptr == NULL)
2791     return -1;
2792
2793   state->domains = new_ptr;
2794   state->domains[state->nr_domains].ptr = dom;
2795   state->domains[state->nr_domains].active = active;
2796   memset(&state->domains[state->nr_domains].info, 0,
2797          sizeof(state->domains[state->nr_domains].info));
2798
2799   return state->nr_domains++;
2800 }
2801
2802 static void free_block_devices(struct lv_read_state *state) {
2803   if (state->block_devices) {
2804     for (int i = 0; i < state->nr_block_devices; ++i)
2805       sfree(state->block_devices[i].path);
2806     sfree(state->block_devices);
2807   }
2808   state->block_devices = NULL;
2809   state->nr_block_devices = 0;
2810 }
2811
2812 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
2813                             const char *path, bool has_source) {
2814
2815   char *path_copy = strdup(path);
2816   if (!path_copy)
2817     return -1;
2818
2819   int new_size =
2820       sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
2821
2822   struct block_device *new_ptr = realloc(state->block_devices, new_size);
2823   if (new_ptr == NULL) {
2824     sfree(path_copy);
2825     return -1;
2826   }
2827   state->block_devices = new_ptr;
2828   state->block_devices[state->nr_block_devices].dom = dom;
2829   state->block_devices[state->nr_block_devices].path = path_copy;
2830   state->block_devices[state->nr_block_devices].has_source = has_source;
2831   return state->nr_block_devices++;
2832 }
2833
2834 static void free_interface_devices(struct lv_read_state *state) {
2835   if (state->interface_devices) {
2836     for (int i = 0; i < state->nr_interface_devices; ++i) {
2837       sfree(state->interface_devices[i].path);
2838       sfree(state->interface_devices[i].address);
2839       sfree(state->interface_devices[i].number);
2840     }
2841     sfree(state->interface_devices);
2842   }
2843   state->interface_devices = NULL;
2844   state->nr_interface_devices = 0;
2845 }
2846
2847 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
2848                                 const char *path, const char *address,
2849                                 unsigned int number) {
2850
2851   if ((path == NULL) || (address == NULL))
2852     return EINVAL;
2853
2854   char *path_copy = strdup(path);
2855   if (!path_copy)
2856     return -1;
2857
2858   char *address_copy = strdup(address);
2859   if (!address_copy) {
2860     sfree(path_copy);
2861     return -1;
2862   }
2863
2864   char number_string[21];
2865   snprintf(number_string, sizeof(number_string), "interface-%u", number);
2866   char *number_copy = strdup(number_string);
2867   if (!number_copy) {
2868     sfree(path_copy);
2869     sfree(address_copy);
2870     return -1;
2871   }
2872
2873   int new_size =
2874       sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
2875
2876   struct interface_device *new_ptr =
2877       realloc(state->interface_devices, new_size);
2878   if (new_ptr == NULL) {
2879     sfree(path_copy);
2880     sfree(address_copy);
2881     sfree(number_copy);
2882     return -1;
2883   }
2884
2885   state->interface_devices = new_ptr;
2886   state->interface_devices[state->nr_interface_devices].dom = dom;
2887   state->interface_devices[state->nr_interface_devices].path = path_copy;
2888   state->interface_devices[state->nr_interface_devices].address = address_copy;
2889   state->interface_devices[state->nr_interface_devices].number = number_copy;
2890   return state->nr_interface_devices++;
2891 }
2892
2893 static int ignore_device_match(ignorelist_t *il, const char *domname,
2894                                const char *devpath) {
2895   if ((domname == NULL) || (devpath == NULL))
2896     return 0;
2897
2898   size_t n = strlen(domname) + strlen(devpath) + 2;
2899   char *name = malloc(n);
2900   if (name == NULL) {
2901     ERROR(PLUGIN_NAME " plugin: malloc failed.");
2902     return 0;
2903   }
2904   snprintf(name, n, "%s:%s", domname, devpath);
2905   int r = ignorelist_match(il, name);
2906   sfree(name);
2907   return r;
2908 }
2909
2910 static int lv_shutdown(void) {
2911   for (int i = 0; i < nr_instances; ++i) {
2912     lv_fini_instance(i);
2913   }
2914
2915   if (!persistent_notification)
2916     stop_event_loop(&notif_thread);
2917
2918   lv_disconnect();
2919
2920   ignorelist_free(il_domains);
2921   il_domains = NULL;
2922   ignorelist_free(il_block_devices);
2923   il_block_devices = NULL;
2924   ignorelist_free(il_interface_devices);
2925   il_interface_devices = NULL;
2926
2927   return 0;
2928 }
2929
2930 void module_register(void) {
2931   plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
2932   plugin_register_init(PLUGIN_NAME, lv_init);
2933   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
2934 }