+static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
+ char *dom_tag) {
+ char xpath_str[BUFFER_MAX_LEN] = {'\0'};
+ xmlXPathObjectPtr xpath_obj = NULL;
+ xmlNodePtr xml_node = NULL;
+ int ret = -1;
+ int err;
+
+ err = xmlXPathRegisterNs(xpath_ctx,
+ (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
+ (const xmlChar *)METADATA_VM_PARTITION_URI);
+ if (err) {
+ ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
+ METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
+ goto done;
+ }
+
+ snprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
+ METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
+ xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
+ if (xpath_obj == NULL) {
+ ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
+ xpath_str, dom_name);
+ goto done;
+ }
+
+ if (xpath_obj->type != XPATH_NODESET) {
+ ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
+ "(wanted %d) on domain %s",
+ xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
+ goto done;
+ }
+
+ /*
+ * from now on there is no real error, it's ok if a domain
+ * doesn't have the metadata partition tag.
+ */
+ ret = 0;
+ if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
+ DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
+ "expected=1 on domain %s",
+ xpath_str,
+ (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
+ dom_name);
+ } else {
+ xml_node = xpath_obj->nodesetval->nodeTab[0];
+ sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
+ }
+
+done:
+ /* deregister to clean up */
+ err = xmlXPathRegisterNs(xpath_ctx,
+ (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
+ if (err) {
+ /* we can't really recover here */
+ ERROR(PLUGIN_NAME
+ " plugin: deregistration of namespace %s failed for domain %s",
+ METADATA_VM_PARTITION_PREFIX, dom_name);
+ }
+ if (xpath_obj)
+ xmlXPathFreeObject(xpath_obj);
+
+ return ret;
+}
+
+static int is_known_tag(const char *dom_tag) {
+ for (int i = 0; i < nr_instances; ++i)
+ if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
+ return 1;
+ return 0;
+}
+
+static int lv_instance_include_domain(struct lv_read_instance *inst,
+ const char *dom_name,
+ const char *dom_tag) {
+ if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
+ return 1;
+
+ /* instance#0 will always be there, so it is in charge of extra duties */
+ if (inst->id == 0) {
+ if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
+ DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
+ "with unknown tag '%s'",
+ inst->tag, dom_name, dom_tag);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void lv_add_block_devices(struct lv_read_state *state, virDomainPtr dom,
+ const char *domname,
+ xmlXPathContextPtr xpath_ctx) {
+ xmlXPathObjectPtr xpath_obj =
+ xmlXPathEval((const xmlChar *)"/domain/devices/disk", xpath_ctx);
+
+ if (xpath_obj == NULL) {
+ DEBUG(PLUGIN_NAME " plugin: no disk xpath-object found for domain %s",
+ domname);
+ return;
+ }
+
+ if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
+ DEBUG(PLUGIN_NAME " plugin: no disk node found for domain %s", domname);
+ goto cleanup;
+ }
+
+ xmlNodeSetPtr xml_block_devices = xpath_obj->nodesetval;
+ for (int i = 0; i < xml_block_devices->nodeNr; ++i) {
+ xmlNodePtr xml_device = xpath_obj->nodesetval->nodeTab[i];
+ char *path_str = NULL;
+ char *source_str = NULL;
+
+ if (!xml_device)
+ continue;
+
+ /* Fetching path and source for block device */
+ for (xmlNodePtr child = xml_device->children; child; child = child->next) {
+ if (child->type != XML_ELEMENT_NODE)
+ continue;
+
+ /* we are interested only in either "target" or "source" elements */
+ if (xmlStrEqual(child->name, (const xmlChar *)"target"))
+ path_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+ else if (xmlStrEqual(child->name, (const xmlChar *)"source")) {
+ /* name of the source is located in "dev" or "file" element (it depends
+ * on type of source). Trying "dev" at first*/
+ source_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+ if (!source_str)
+ source_str = (char *)xmlGetProp(child, (const xmlChar *)"file");
+ }
+ /* ignoring any other element*/
+ }
+
+ /* source_str will be interpreted as a device path if blockdevice_format
+ * param is set to 'source'. */
+ const char *device_path =
+ (blockdevice_format == source) ? source_str : path_str;
+
+ if (!device_path) {
+ /* no path found and we can't add block_device without it */
+ WARNING(PLUGIN_NAME " plugin: could not generate device path for disk in "
+ "domain %s - disk device will be ignored in reports",
+ domname);
+ goto cont;
+ }
+
+ if (ignore_device_match(il_block_devices, domname, device_path) == 0) {
+ /* we only have to store information whether 'source' exists or not */
+ bool has_source = (source_str != NULL) ? true : false;
+
+ add_block_device(state, dom, device_path, has_source);
+ }
+
+ cont:
+ if (path_str)
+ xmlFree(path_str);
+
+ if (source_str)
+ xmlFree(source_str);
+ }
+
+cleanup:
+ xmlXPathFreeObject(xpath_obj);
+}
+
+static void lv_add_network_interfaces(struct lv_read_state *state,
+ virDomainPtr dom, const char *domname,
+ xmlXPathContextPtr xpath_ctx) {
+ xmlXPathObjectPtr xpath_obj = xmlXPathEval(
+ (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
+
+ if (xpath_obj == NULL)
+ return;
+
+ if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
+ xmlXPathFreeObject(xpath_obj);
+ return;
+ }
+
+ xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
+
+ for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
+ char *path = NULL;
+ char *address = NULL;
+ const int itf_number = j + 1;
+
+ xmlNodePtr xml_interface = xml_interfaces->nodeTab[j];
+ if (!xml_interface)
+ continue;
+
+ for (xmlNodePtr child = xml_interface->children; child;
+ child = child->next) {
+ if (child->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
+ path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+ if (!path)
+ continue;
+ } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
+ address = (char *)xmlGetProp(child, (const xmlChar *)"address");
+ if (!address)
+ continue;
+ }
+ }
+
+ bool device_ignored = false;
+ switch (interface_format) {
+ case if_name:
+ if (ignore_device_match(il_interface_devices, domname, path) != 0)
+ device_ignored = true;
+ break;
+ case if_address:
+ if (ignore_device_match(il_interface_devices, domname, address) != 0)
+ device_ignored = true;
+ break;
+ case if_number: {
+ char number_string[4];
+ snprintf(number_string, sizeof(number_string), "%d", itf_number);
+ if (ignore_device_match(il_interface_devices, domname, number_string) !=
+ 0)
+ device_ignored = true;
+ } break;
+ default:
+ ERROR(PLUGIN_NAME " plugin: Unknown interface_format option: %d",
+ interface_format);
+ }
+
+ if (!device_ignored)
+ add_interface_device(state, dom, path, address, itf_number);
+
+ if (path)
+ xmlFree(path);
+ if (address)
+ xmlFree(address);
+ }
+ xmlXPathFreeObject(xpath_obj);
+}
+
+static bool is_domain_ignored(virDomainPtr dom) {
+ const char *domname = virDomainGetName(dom);
+
+ if (domname == NULL) {
+ VIRT_ERROR(conn, "virDomainGetName failed, ignoring domain");
+ return true;
+ }
+
+ if (ignorelist_match(il_domains, domname) != 0) {
+ DEBUG(PLUGIN_NAME
+ " plugin: ignoring domain '%s' because of ignorelist option",
+ domname);
+ return true;
+ }
+
+ return false;
+}
+
+static int refresh_lists(struct lv_read_instance *inst) {
+ struct lv_read_state *state = &inst->read_state;
+ int n;
+
+#ifndef HAVE_LIST_ALL_DOMAINS
+ n = virConnectNumOfDomains(conn);
+ if (n < 0) {
+ VIRT_ERROR(conn, "reading number of domains");
+ return -1;
+ }
+#endif
+
+ lv_clean_read_state(state);
+
+#ifndef HAVE_LIST_ALL_DOMAINS
+ if (n == 0)
+ goto end;
+#endif
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+ virDomainPtr *domains, *domains_inactive;
+ int m = virConnectListAllDomains(conn, &domains_inactive,
+ VIR_CONNECT_LIST_DOMAINS_INACTIVE);
+ n = virConnectListAllDomains(conn, &domains, VIR_CONNECT_LIST_DOMAINS_ACTIVE);
+#else
+ /* Get list of domains. */
+ int *domids = calloc(n, sizeof(*domids));
+ if (domids == NULL) {
+ ERROR(PLUGIN_NAME " plugin: calloc failed.");
+ return -1;
+ }
+
+ n = virConnectListDomains(conn, domids, n);
+#endif
+
+ if (n < 0) {
+ VIRT_ERROR(conn, "reading list of domains");
+#ifndef HAVE_LIST_ALL_DOMAINS
+ sfree(domids);
+#else
+ for (int i = 0; i < m; ++i)
+ virDomainFree(domains_inactive[i]);
+ sfree(domains_inactive);
+#endif
+ return -1;
+ }
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+ for (int i = 0; i < m; ++i)
+ if (is_domain_ignored(domains_inactive[i]) ||
+ add_domain(state, domains_inactive[i], 0) < 0) {
+ /* domain ignored or failed during adding to domains list*/
+ virDomainFree(domains_inactive[i]);
+ domains_inactive[i] = NULL;
+ continue;
+ }
+#endif
+
+ /* Fetch each domain and add it to the list, unless ignore. */
+ for (int i = 0; i < n; ++i) {
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+ virDomainPtr dom = domains[i];
+#else
+ virDomainPtr dom = virDomainLookupByID(conn, domids[i]);
+ if (dom == NULL) {
+ VIRT_ERROR(conn, "virDomainLookupByID");
+ /* Could be that the domain went away -- ignore it anyway. */
+ continue;
+ }
+#endif
+
+ if (is_domain_ignored(dom) || add_domain(state, dom, 1) < 0) {
+ /*
+ * domain ignored or failed during adding to domains list
+ *
+ * When domain is already tracked, then there is
+ * no problem with memory handling (will be freed
+ * with the rest of domains cached data)
+ * But in case of error like this (error occurred
+ * before adding domain to track) we have to take
+ * care it ourselves and call virDomainFree
+ */
+ virDomainFree(dom);
+ continue;
+ }
+
+ const char *domname = virDomainGetName(dom);
+ if (domname == NULL) {
+ VIRT_ERROR(conn, "virDomainGetName");
+ continue;
+ }
+
+ virDomainInfo info;
+ int status = virDomainGetInfo(dom, &info);
+ if (status != 0) {
+ ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
+ status);
+ continue;
+ }
+
+ if (info.state != VIR_DOMAIN_RUNNING) {
+ DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", domname);
+ continue;
+ }
+
+ /* Get a list of devices for this domain. */
+ xmlDocPtr xml_doc = NULL;
+ xmlXPathContextPtr xpath_ctx = NULL;
+
+ char *xml = virDomainGetXMLDesc(dom, 0);
+ if (!xml) {
+ VIRT_ERROR(conn, "virDomainGetXMLDesc");
+ goto cont;
+ }
+
+ /* Yuck, XML. Parse out the devices. */
+ xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
+ if (xml_doc == NULL) {
+ VIRT_ERROR(conn, "xmlReadDoc");
+ goto cont;
+ }
+
+ xpath_ctx = xmlXPathNewContext(xml_doc);
+
+ char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
+ if (lv_domain_get_tag(xpath_ctx, domname, tag) < 0) {
+ ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
+ goto cont;
+ }
+
+ if (!lv_instance_include_domain(inst, domname, tag))
+ goto cont;
+
+ /* Block devices. */
+ if (report_block_devices)
+ lv_add_block_devices(state, dom, domname, xpath_ctx);
+
+ /* Network interfaces. */
+ if (report_network_interfaces)
+ lv_add_network_interfaces(state, dom, domname, xpath_ctx);
+
+ cont:
+ if (xpath_ctx)
+ xmlXPathFreeContext(xpath_ctx);
+ if (xml_doc)
+ xmlFreeDoc(xml_doc);
+ sfree(xml);
+ }
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+ /* NOTE: domains_active and domains_inactive data will be cleared during
+ refresh of all domains (inside lv_clean_read_state function) so we need
+ to free here only allocated arrays */
+ sfree(domains);
+ sfree(domains_inactive);
+#else
+ sfree(domids);
+
+end:
+#endif
+
+ DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
+ " domains=%i block_devices=%i iface_devices=%i",
+ inst->tag, state->nr_domains, state->nr_block_devices,
+ state->nr_interface_devices);
+
+ return 0;
+}
+
+static void free_domains(struct lv_read_state *state) {
+ if (state->domains) {
+ for (int i = 0; i < state->nr_domains; ++i)
+ virDomainFree(state->domains[i].ptr);
+ sfree(state->domains);
+ }
+ state->domains = NULL;
+ state->nr_domains = 0;
+}
+
+static int add_domain(struct lv_read_state *state, virDomainPtr dom,
+ bool active) {
+ int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
+
+ domain_t *new_ptr = realloc(state->domains, new_size);
+ if (new_ptr == NULL) {
+ ERROR(PLUGIN_NAME " plugin: realloc failed in add_domain()");
+ return -1;
+ }
+
+ state->domains = new_ptr;
+ state->domains[state->nr_domains].ptr = dom;
+ state->domains[state->nr_domains].active = active;
+ memset(&state->domains[state->nr_domains].info, 0,
+ sizeof(state->domains[state->nr_domains].info));
+
+ return state->nr_domains++;
+}
+
+static void free_block_devices(struct lv_read_state *state) {
+ if (state->block_devices) {
+ for (int i = 0; i < state->nr_block_devices; ++i)
+ sfree(state->block_devices[i].path);
+ sfree(state->block_devices);
+ }
+ state->block_devices = NULL;
+ state->nr_block_devices = 0;
+}
+
+static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
+ const char *path, bool has_source) {
+
+ char *path_copy = strdup(path);
+ if (!path_copy)
+ return -1;
+
+ int new_size =
+ sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
+
+ struct block_device *new_ptr = realloc(state->block_devices, new_size);
+ if (new_ptr == NULL) {
+ sfree(path_copy);
+ return -1;
+ }
+ state->block_devices = new_ptr;
+ state->block_devices[state->nr_block_devices].dom = dom;
+ state->block_devices[state->nr_block_devices].path = path_copy;
+ state->block_devices[state->nr_block_devices].has_source = has_source;
+ return state->nr_block_devices++;
+}
+
+static void free_interface_devices(struct lv_read_state *state) {
+ if (state->interface_devices) {
+ for (int i = 0; i < state->nr_interface_devices; ++i) {
+ sfree(state->interface_devices[i].path);
+ sfree(state->interface_devices[i].address);
+ sfree(state->interface_devices[i].number);
+ }
+ sfree(state->interface_devices);
+ }
+ state->interface_devices = NULL;
+ state->nr_interface_devices = 0;
+}
+
+static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
+ const char *path, const char *address,
+ unsigned int number) {
+
+ if ((path == NULL) || (address == NULL))
+ return EINVAL;
+
+ char *path_copy = strdup(path);
+ if (!path_copy)
+ return -1;
+
+ char *address_copy = strdup(address);
+ if (!address_copy) {
+ sfree(path_copy);
+ return -1;
+ }
+
+ char number_string[21];
+ snprintf(number_string, sizeof(number_string), "interface-%u", number);
+ char *number_copy = strdup(number_string);
+ if (!number_copy) {
+ sfree(path_copy);
+ sfree(address_copy);
+ return -1;
+ }
+
+ int new_size =
+ sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
+
+ struct interface_device *new_ptr =
+ realloc(state->interface_devices, new_size);
+ if (new_ptr == NULL) {
+ sfree(path_copy);
+ sfree(address_copy);
+ sfree(number_copy);
+ return -1;
+ }
+
+ state->interface_devices = new_ptr;
+ state->interface_devices[state->nr_interface_devices].dom = dom;
+ state->interface_devices[state->nr_interface_devices].path = path_copy;
+ state->interface_devices[state->nr_interface_devices].address = address_copy;
+ state->interface_devices[state->nr_interface_devices].number = number_copy;
+ return state->nr_interface_devices++;
+}
+
+static int ignore_device_match(ignorelist_t *il, const char *domname,
+ const char *devpath) {
+ if ((domname == NULL) || (devpath == NULL))
+ return 0;
+
+ size_t n = strlen(domname) + strlen(devpath) + 2;
+ char *name = malloc(n);
+ if (name == NULL) {
+ ERROR(PLUGIN_NAME " plugin: malloc failed.");
+ return 0;
+ }
+ snprintf(name, n, "%s:%s", domname, devpath);
+ int r = ignorelist_match(il, name);
+ sfree(name);
+ return r;
+}
+
+static int lv_shutdown(void) {
+ for (int i = 0; i < nr_instances; ++i) {
+ lv_fini_instance(i);
+ }
+
+ if (!persistent_notification)
+ stop_event_loop(¬if_thread);
+
+ lv_disconnect();
+
+ ignorelist_free(il_domains);
+ il_domains = NULL;
+ ignorelist_free(il_block_devices);
+ il_block_devices = NULL;
+ ignorelist_free(il_interface_devices);
+ il_interface_devices = NULL;
+
+ return 0;
+}
+
+void module_register(void) {
+ plugin_register_complex_config("virt", lv_config);
+ plugin_register_init(PLUGIN_NAME, lv_init);
+ plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
+}