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