static void virDomainCapsCPUModelsDispose(void *obj) { virDomainCapsCPUModelsPtr cpuModels = obj; size_t i; for (i = 0; i < cpuModels->nmodels; i++) { VIR_FREE(cpuModels->models[i].name); virStringListFree(cpuModels->models[i].blockers); } VIR_FREE(cpuModels->models); }
static int testSplit(const void *args) { const struct testSplitData *data = args; char **got; size_t ntokens; size_t exptokens = 0; char **tmp1; const char **tmp2; int ret = -1; if (!(got = virStringSplitCount(data->string, data->delim, data->max_tokens, &ntokens))) { VIR_DEBUG("Got no tokens at all"); return -1; } tmp1 = got; tmp2 = data->tokens; while (*tmp1 && *tmp2) { if (STRNEQ(*tmp1, *tmp2)) { virFilePrintf(stderr, "Mismatch '%s' vs '%s'\n", *tmp1, *tmp2); goto cleanup; } tmp1++; tmp2++; exptokens++; } if (*tmp1) { virFilePrintf(stderr, "Too many pieces returned\n"); goto cleanup; } if (*tmp2) { virFilePrintf(stderr, "Too few pieces returned\n"); goto cleanup; } if (ntokens != exptokens) { virFilePrintf(stderr, "Returned token count (%zu) doesn't match " "expected count (%zu)", ntokens, exptokens); goto cleanup; } ret = 0; cleanup: virStringListFree(got); return ret; }
static int virStorageBackendZFSFindVols(virStoragePoolObjPtr pool, virStorageVolDefPtr vol) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); virCommandPtr cmd = NULL; char *volumes_list = NULL; char **lines = NULL; size_t i; /** * $ zfs list -Hp -t volume -o name,volsize -r test * test/vol1 5368709120 * test/vol3 1073741824 * test/vol4 1572864000 * $ * * Arguments description: * -t volume -- we want to see only volumes * -o name,volsize -- limit output to name and volume size * -r -- we want to see all the childer of our pool */ cmd = virCommandNewArgList(ZFS, "list", "-Hp", "-t", "volume", "-r", "-o", "name,volsize,refreservation", def->source.name, NULL); virCommandSetOutputBuffer(cmd, &volumes_list); if (virCommandRun(cmd, NULL) < 0) goto cleanup; if (!(lines = virStringSplit(volumes_list, "\n", 0))) goto cleanup; for (i = 0; lines[i]; i++) { if (STREQ(lines[i], "")) continue; if (virStorageBackendZFSParseVol(pool, vol, lines[i]) < 0) continue; } cleanup: virCommandFree(cmd); virStringListFree(lines); VIR_FREE(volumes_list); return 0; }
static int testAdd(const void *args) { const struct testJoinData *data = args; char **list = NULL; char *got = NULL; int ret = -1; size_t i; for (i = 0; data->tokens[i]; i++) { char **tmp = virStringListAdd((const char **)list, data->tokens[i]); if (!tmp) goto cleanup; virStringListFree(list); list = tmp; tmp = NULL; } if (!list && VIR_ALLOC(list) < 0) goto cleanup; if (!(got = virStringListJoin((const char **)list, data->delim))) { VIR_DEBUG("Got no result"); goto cleanup; } if (STRNEQ(got, data->string)) { virFilePrintf(stderr, "Mismatch '%s' vs '%s'\n", got, data->string); goto cleanup; } ret = 0; cleanup: virStringListFree(list); VIR_FREE(got); return ret; }
static int lxcNetworkParseDataIPs(const char *name, virConfValuePtr value, lxcNetworkParseData *parseData) { int family = AF_INET; char **ipparts = NULL; virNetDevIPAddrPtr ip = NULL; if (VIR_ALLOC(ip) < 0) return -1; if (STREQ(name, "ipv6")) family = AF_INET6; ipparts = virStringSplit(value->str, "/", 2); if (virStringListLength((const char * const *)ipparts) != 2 || virSocketAddrParse(&ip->address, ipparts[0], family) < 0 || virStrToLong_ui(ipparts[1], NULL, 10, &ip->prefix) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("Invalid CIDR address: '%s'"), value->str); virStringListFree(ipparts); VIR_FREE(ip); return -1; } virStringListFree(ipparts); if (VIR_APPEND_ELEMENT(parseData->ips, parseData->nips, ip) < 0) { VIR_FREE(ip); return -1; } return 0; }
static bool cmdNodeDeviceDestroy(vshControl *ctl, const vshCmd *cmd) { virNodeDevicePtr dev = NULL; bool ret = false; const char *device_value = NULL; char **arr = NULL; int narr; virshControlPtr priv = ctl->privData; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; if (strchr(device_value, ',')) { narr = vshStringToArray(device_value, &arr); if (narr != 2) { vshError(ctl, _("Malformed device value '%s'"), device_value); goto cleanup; } if (!virValidateWWN(arr[0]) || !virValidateWWN(arr[1])) goto cleanup; dev = virNodeDeviceLookupSCSIHostByWWN(priv->conn, arr[0], arr[1], 0); } else { dev = virNodeDeviceLookupByName(priv->conn, device_value); } if (!dev) { vshError(ctl, "%s '%s'", _("Could not find matching device"), device_value); goto cleanup; } if (virNodeDeviceDestroy(dev) == 0) { vshPrintExtra(ctl, _("Destroyed node device '%s'\n"), device_value); } else { vshError(ctl, _("Failed to destroy node device '%s'"), device_value); goto cleanup; } ret = true; cleanup: virStringListFree(arr); if (dev) virNodeDeviceFree(dev); return ret; }
static void lxcSetCapDrop(virDomainDefPtr def, virConfPtr properties) { VIR_AUTOFREE(char *) value = NULL; char **toDrop = NULL; const char *capString; size_t i; if (virConfGetValueString(properties, "lxc.cap.drop", &value) > 0) toDrop = virStringSplit(value, " ", 0); for (i = 0; i < VIR_DOMAIN_CAPS_FEATURE_LAST; i++) { capString = virDomainCapsFeatureTypeToString(i); if (toDrop != NULL && virStringListHasString((const char **)toDrop, capString)) def->caps_features[i] = VIR_TRISTATE_SWITCH_OFF; } def->features[VIR_DOMAIN_FEATURE_CAPABILITIES] = VIR_DOMAIN_CAPABILITIES_POLICY_ALLOW; virStringListFree(toDrop); }
/** * virStringSearch: * @str: string to search * @regexp: POSIX Extended regular expression pattern used for matching * @max_matches: maximum number of substrings to return * @result: pointer to an array to be filled with NULL terminated list of matches * * Performs a POSIX extended regex search against a string and return all matching substrings. * The @result value should be freed with virStringListFree() when no longer * required. * * @code * char *source = "6853a496-1c10-472e-867a-8244937bd6f0 * 773ab075-4cd7-4fc2-8b6e-21c84e9cb391 * bbb3c75c-d60f-43b0-b802-fd56b84a4222 * 60c04aa1-0375-4654-8d9f-e149d9885273 * 4548d465-9891-4c34-a184-3b1c34a26aa8"; * char **matches = NULL; * virStringSearch(source, * "([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})", * 3, * &matches); * * // matches[0] == "6853a496-1c10-472e-867a-8244937bd6f0"; * // matches[1] == "773ab075-4cd7-4fc2-8b6e-21c84e9cb391"; * // matches[2] == "bbb3c75c-d60f-43b0-b802-fd56b84a4222" * // matches[3] == NULL; * * virStringListFree(matches); * @endcode * * Returns: -1 on error, or number of matches */ ssize_t virStringSearch(const char *str, const char *regexp, size_t max_matches, char ***matches) { regex_t re; regmatch_t rem; size_t nmatches = 0; ssize_t ret = -1; int rv = -1; *matches = NULL; VIR_DEBUG("search '%s' for '%s'", str, regexp); if ((rv = regcomp(&re, regexp, REG_EXTENDED)) != 0) { char error[100]; regerror(rv, &re, error, sizeof(error)); virReportError(VIR_ERR_INTERNAL_ERROR, _("Error while compiling regular expression '%s': %s"), regexp, error); return -1; } if (re.re_nsub != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Regular expression '%s' must have exactly 1 match group, not %zu"), regexp, re.re_nsub); goto cleanup; } /* '*matches' must always be NULL terminated in every iteration * of the loop, so start by allocating 1 element */ if (VIR_EXPAND_N(*matches, nmatches, 1) < 0) goto cleanup; while ((nmatches - 1) < max_matches) { char *match; if (regexec(&re, str, 1, &rem, 0) != 0) break; if (VIR_EXPAND_N(*matches, nmatches, 1) < 0) goto cleanup; if (VIR_STRNDUP(match, str + rem.rm_so, rem.rm_eo - rem.rm_so) < 0) goto cleanup; VIR_DEBUG("Got '%s'", match); (*matches)[nmatches-2] = match; str = str + rem.rm_eo; } ret = nmatches - 1; /* don't count the trailing null */ cleanup: regfree(&re); if (ret < 0) { virStringListFree(*matches); *matches = NULL; } return ret; }
static int virStorageBackendZFSRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED, virStoragePoolObjPtr pool ATTRIBUTE_UNUSED) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); virCommandPtr cmd = NULL; char *zpool_props = NULL; char **lines = NULL; char **tokens = NULL; size_t i; /** * $ zpool get -Hp health,size,free,allocated test * test health ONLINE - * test size 199715979264 - * test free 198899976704 - * test allocated 816002560 - * $ * * Here we just provide a list of properties we want to see */ cmd = virCommandNewArgList(ZPOOL, "get", "-Hp", "health,size,free,allocated", def->source.name, NULL); virCommandSetOutputBuffer(cmd, &zpool_props); if (virCommandRun(cmd, NULL) < 0) goto cleanup; if (!(lines = virStringSplit(zpool_props, "\n", 0))) goto cleanup; for (i = 0; lines[i]; i++) { size_t count; char *prop_name; if (STREQ(lines[i], "")) continue; virStringListFree(tokens); if (!(tokens = virStringSplitCount(lines[i], "\t", 0, &count))) goto cleanup; if (count != 4) continue; prop_name = tokens[1]; if (STREQ(prop_name, "free") || STREQ(prop_name, "size") || STREQ(prop_name, "allocated")) { unsigned long long value; if (virStrToLong_ull(tokens[2], NULL, 10, &value) < 0) goto cleanup; if (STREQ(prop_name, "free")) def->available = value; else if (STREQ(prop_name, "size")) def->capacity = value; else if (STREQ(prop_name, "allocated")) def->allocation = value; } } /* Obtain a list of volumes */ if (virStorageBackendZFSFindVols(pool, NULL) < 0) goto cleanup; cleanup: virCommandFree(cmd); virStringListFree(lines); virStringListFree(tokens); VIR_FREE(zpool_props); return 0; }
static int virStorageBackendZFSParseVol(virStoragePoolObjPtr pool, virStorageVolDefPtr vol, const char *volume_string) { int ret = -1; char **tokens; size_t count; char **name_tokens = NULL; char *vol_name; bool is_new_vol = false; virStorageVolDefPtr volume = NULL; virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); if (!(tokens = virStringSplitCount(volume_string, "\t", 0, &count))) return -1; if (count != 3) goto cleanup; if (!(name_tokens = virStringSplit(tokens[0], "/", 2))) goto cleanup; vol_name = name_tokens[1]; if (vol == NULL) volume = virStorageVolDefFindByName(pool, vol_name); else volume = vol; if (volume == NULL) { if (VIR_ALLOC(volume) < 0) goto cleanup; is_new_vol = true; volume->type = VIR_STORAGE_VOL_BLOCK; if (VIR_STRDUP(volume->name, vol_name) < 0) goto cleanup; } if (!volume->key && VIR_STRDUP(volume->key, tokens[0]) < 0) goto cleanup; if (volume->target.path == NULL) { if (virAsprintf(&volume->target.path, "%s/%s", def->target.path, volume->name) < 0) goto cleanup; } if (virStrToLong_ull(tokens[1], NULL, 10, &volume->target.capacity) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed volsize reported")); goto cleanup; } if (virStrToLong_ull(tokens[2], NULL, 10, &volume->target.allocation) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed refreservation reported")); goto cleanup; } if (volume->target.allocation < volume->target.capacity) volume->target.sparse = true; if (is_new_vol && virStoragePoolObjAddVol(pool, volume) < 0) goto cleanup; volume = NULL; ret = 0; cleanup: virStringListFree(tokens); virStringListFree(name_tokens); if (is_new_vol) virStorageVolDefFree(volume); return ret; }
static int lxctoolsReadFSConfig(lxctoolsConffilePtr conffile, virDomainDefPtr def) { virDomainFSDefPtr fs = NULL; char* item_str = NULL; size_t tokencnt; char** splitlist = NULL; if ((item_str = lxctoolsConffileGetItem(conffile, "lxc.rootfs.path")) == NULL || item_str[0] == '\0') { VIR_WARN("Could not find key lxc.rootfs.path. Trying legacy key lxc.rootfs."); if ((item_str = lxctoolsConffileGetItem(conffile, "lxc.rootfs")) == NULL) { VIR_ERROR("Could not find key lxc.rootfs"); goto error; } } if (item_str[0] == '\0') { VIR_ERROR("Domain has no rootfs config-item."); goto error; } if (VIR_ALLOC(fs) < 0) { VIR_ERROR("Could not allocate for virDomainFSDefPtr fs."); goto error; } fs->type = VIR_DOMAIN_FS_TYPE_MOUNT; if (VIR_ALLOC(fs->src) < 0) { VIR_ERROR("Could not allocate for virStorageSourcePtr fs->src."); goto error; } splitlist = virStringSplitCount(item_str, ":", 3, &tokencnt); // Simple path if (tokencnt == 1) { fs->fsdriver = VIR_DOMAIN_FS_DRIVER_TYPE_PATH; if (virAsprintf(&fs->src->path, "dir:%s", splitlist[0]) < 0) { VIR_ERROR("Could not print string in fs->src->path."); goto error; } // dir } else if (tokencnt == 2 && strcmp(splitlist[0], "dir") == 0) { fs->fsdriver = VIR_DOMAIN_FS_DRIVER_TYPE_PATH; if (virAsprintf(&fs->src->path, "%s:%s", splitlist[0], splitlist[1]) < 0) { VIR_ERROR("Could not print string in fs->src->path."); goto error; } // overlay or overlayfs } else if (tokencnt == 3 && (strcmp(splitlist[0], "overlay") == 0 || strcmp(splitlist[0], "overlayfs") == 0)) { // We could add a new FS type like VIR_DOMAIN_FS_DRIVER_TYPE_OVERLAY, but this requires minor changes in the // libvirt interface and the qemu driver. // Also, we could either save "lowerdir:upperdir" in fs->src or create additional members to hold each seperately // splitlist[0] -> overlayfs/overlay // splitlist[1] -> lowerdir path // splitlist[2] -> upperdir path // For now, this implements overlayfs using VIR_DOMAIN_FS_DRIVER_TYPE_PATH and saving "overlayfs:lowerdir:upperdir" // in fs->src, which does not require lxctools external changes, but is a little bit ugly. fs->fsdriver = VIR_DOMAIN_FS_DRIVER_TYPE_PATH; if (virAsprintf(&fs->src->path, "%s:%s:%s", splitlist[0], splitlist[1], splitlist[2]) < 0) { VIR_ERROR("Could not print string in fs->src->path."); goto error; } } else { VIR_ERROR("Domain rootfs type is currently not supported"); goto error; } if (VIR_STRDUP(fs->dst, "/") != 1) { VIR_ERROR("Could not duplicate string."); goto error; } if (virDomainFSInsert(def, fs) < 0) { VIR_ERROR("Could not insert filesystem desc into domain."); goto error; } fs = NULL; VIR_FREE(item_str); virStringListFree(splitlist); splitlist = NULL; if ((splitlist = lxctoolsConffileGetItemlist(conffile, "lxc.mount.entry", &tokencnt)) == NULL) { goto error; } if (splitlist[0] != NULL) { size_t param_cnt; char** params; while (tokencnt-- > 0) { params = virStringSplitCount(splitlist[tokencnt], " ", 6, ¶m_cnt); if (param_cnt != 6) { VIR_ERROR("The following entry has to few parameters: '%s'", splitlist[tokencnt]); goto error; } if (VIR_ALLOC(fs) < 0) { VIR_ERROR("Could not allocate for virDomainFSDefPtr fs."); goto error; } if (strcmp(params[2], "none") == 0 && strstr(params[3],"bind") != NULL) { fs->type = VIR_DOMAIN_FS_TYPE_MOUNT; fs->fsdriver = VIR_DOMAIN_FS_DRIVER_TYPE_PATH; if (VIR_ALLOC(fs->src) < 0) { VIR_ERROR("Could not allocate for virStorageSourcePtr fs->src."); goto error; } if (VIR_STRDUP(fs->src->path, params[0]) < 0) { VIR_ERROR("Could not copy src string."); goto error; } if (virAsprintf(&fs->dst, "/%s", params[1]) < 0) { VIR_ERROR("Could not copy dst string."); goto error; } if (strstr(params[3], "ro") != NULL) { fs->readonly = true; } if (virDomainFSInsert(def, fs) < 0) { VIR_ERROR("Could not insert filesystem desc into domain."); goto error; } fs = NULL; } VIR_FREE(fs); virStringListFree(params); } virStringListFree(splitlist); splitlist = NULL; } return 0; error: virStringListFree(splitlist); splitlist = NULL; VIR_FREE(item_str); if (fs) VIR_FREE(fs->src); VIR_FREE(fs); return -1; }
/** * virConfGetValueStringList: * @conf: the config object * @setting: the config entry name * @compatString: true to treat string entry as a 1 element list * @value: pointer to hold NULL terminated string list * * Get the string list value of the config name @setting, storing * it in @value. If the config entry is not present, then * @value will be unmodified. If @compatString is set to true * and the value is present as a string, this will be turned into * a 1 element list. The returned @value will be NULL terminated * if set. * * Reports an error if the config entry is set but has * an unexpected type. * * Returns: 1 if the value was present, 0 if missing, -1 on error */ int virConfGetValueStringList(virConfPtr conf, const char *setting, bool compatString, char ***values) { virConfValuePtr cval = virConfGetValue(conf, setting); size_t len; virConfValuePtr eval; VIR_DEBUG("Get value string list %p %d", cval, cval ? cval->type : VIR_CONF_NONE); if (!cval) return 0; virStringListFree(*values); *values = NULL; switch (cval->type) { case VIR_CONF_LIST: /* Calc length and check items */ for (len = 0, eval = cval->list; eval; len++, eval = eval->next) { if (eval->type != VIR_CONF_STRING) { virReportError(VIR_ERR_CONF_SYNTAX, _("%s: expected a string list for '%s' parameter"), conf->filename, setting); return -1; } } if (VIR_ALLOC_N(*values, len + 1) < 0) return -1; for (len = 0, eval = cval->list; eval; len++, eval = eval->next) { if (VIR_STRDUP((*values)[len], eval->str) < 0) { virStringListFree(*values); *values = NULL; return -1; } } break; case VIR_CONF_STRING: if (compatString) { if (VIR_ALLOC_N(*values, cval->str ? 2 : 1) < 0) return -1; if (cval->str && VIR_STRDUP((*values)[0], cval->str) < 0) { VIR_FREE(*values); return -1; } break; } /* fallthrough */ default: virReportError(VIR_ERR_INTERNAL_ERROR, compatString ? _("%s: expected a string or string list for '%s' parameter") : _("%s: expected a string list for '%s' parameter"), conf->filename, setting); return -1; } return 1; }
static int lxcBlkioDeviceWalkCallback(const char *name, virConfValuePtr value, void *data) { char **parts = NULL; virBlkioDevicePtr device = NULL; virDomainDefPtr def = data; size_t i = 0; char *path = NULL; int ret = -1; if (!STRPREFIX(name, "lxc.cgroup.blkio.") || STREQ(name, "lxc.cgroup.blkio.weight")|| !value->str) return 0; if (!(parts = lxcStringSplit(value->str))) return -1; if (!parts[0] || !parts[1]) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid %s value: '%s'"), name, value->str); goto cleanup; } if (virAsprintf(&path, "/dev/block/%s", parts[0]) < 0) goto cleanup; /* Do we already have a device definition for this path? * Get that device or create a new one */ for (i = 0; !device && i < def->blkio.ndevices; i++) { if (STREQ(def->blkio.devices[i].path, path)) device = &def->blkio.devices[i]; } if (!device) { if (VIR_EXPAND_N(def->blkio.devices, def->blkio.ndevices, 1) < 0) goto cleanup; device = &def->blkio.devices[def->blkio.ndevices - 1]; device->path = path; path = NULL; } /* Set the value */ if (STREQ(name, "lxc.cgroup.blkio.device_weight")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->weight) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse device weight: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.read_bps_device")) { if (virStrToLong_ull(parts[1], NULL, 10, &device->rbps) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse read_bps_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.write_bps_device")) { if (virStrToLong_ull(parts[1], NULL, 10, &device->wbps) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse write_bps_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.read_iops_device")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->riops) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse read_iops_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.write_iops_device")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->wiops) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse write_iops_device: '%s'"), parts[1]); goto cleanup; } } else { VIR_WARN("Unhandled blkio tune config: %s", name); } ret = 0; cleanup: virStringListFree(parts); VIR_FREE(path); return ret; }
static int lxcAddFstabLine(virDomainDefPtr def, lxcFstabPtr fstab) { const char *src = NULL; char *dst = NULL; char **options = virStringSplit(fstab->options, ",", 0); bool readonly; int type = VIR_DOMAIN_FS_TYPE_MOUNT; unsigned long long usage = 0; int ret = -1; if (!options) return -1; if (fstab->dst[0] != '/') { if (virAsprintf(&dst, "/%s", fstab->dst) < 0) goto cleanup; } else { if (VIR_STRDUP(dst, fstab->dst) < 0) goto cleanup; } /* Check that we don't add basic mounts */ if (lxcIsBasicMountLocation(dst)) { ret = 0; goto cleanup; } if (STREQ(fstab->type, "tmpfs")) { char *sizeStr = NULL; size_t i; type = VIR_DOMAIN_FS_TYPE_RAM; for (i = 0; options[i]; i++) { if ((sizeStr = STRSKIP(options[i], "size="))) { if (lxcConvertSize(sizeStr, &usage) < 0) goto cleanup; break; } } if (!sizeStr) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing tmpfs size, set the size option")); goto cleanup; } } else { src = fstab->src; } /* Is it a block device that needs special favor? */ if (STRPREFIX(fstab->src, "/dev/")) type = VIR_DOMAIN_FS_TYPE_BLOCK; /* Do we have ro in options? */ readonly = virStringListHasString((const char **)options, "ro"); if (lxcAddFSDef(def, type, src, dst, readonly, usage) < 0) goto cleanup; ret = 1; cleanup: VIR_FREE(dst); virStringListFree(options); return ret; }