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