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