/* * virqemu.c: utilities for working with qemu and its tools * * Copyright (C) 2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * */ #include #include "virbuffer.h" #include "virerror.h" #include "virlog.h" #include "virqemu.h" #include "virstring.h" #include "viralloc.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("util.qemu"); struct virQEMUCommandLineJSONIteratorData { const char *prefix; virBufferPtr buf; const char *skipKey; bool onOff; virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc; }; static int virQEMUBuildCommandLineJSONRecurse(const char *key, virJSONValuePtr value, virBufferPtr buf, const char *skipKey, bool onOff, virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, bool nested); int virQEMUBuildCommandLineJSONArrayBitmap(const char *key, virJSONValuePtr array, virBufferPtr buf, const char *skipKey G_GNUC_UNUSED, bool onOff G_GNUC_UNUSED) { ssize_t pos = -1; ssize_t end; g_autoptr(virBitmap) bitmap = NULL; if (virJSONValueGetArrayAsBitmap(array, &bitmap) < 0) return -1; while ((pos = virBitmapNextSetBit(bitmap, pos)) > -1) { if ((end = virBitmapNextClearBit(bitmap, pos)) < 0) end = virBitmapLastSetBit(bitmap) + 1; if (end - 1 > pos) { virBufferAsprintf(buf, "%s=%zd-%zd,", key, pos, end - 1); pos = end; } else { virBufferAsprintf(buf, "%s=%zd,", key, pos); } } return 0; } int virQEMUBuildCommandLineJSONArrayNumbered(const char *key, virJSONValuePtr array, virBufferPtr buf, const char *skipKey, bool onOff) { virJSONValuePtr member; size_t i; for (i = 0; i < virJSONValueArraySize(array); i++) { member = virJSONValueArrayGet((virJSONValuePtr) array, i); g_autofree char *prefix = NULL; prefix = g_strdup_printf("%s.%zu", key, i); if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey, onOff, virQEMUBuildCommandLineJSONArrayNumbered, true) < 0) return 0; } return 0; } /** * This array convertor is for quirky cases where the QMP schema mandates an * array of objects with only one attribute 'str' which needs to be formatted as * repeated key-value pairs without the 'str' being printed: * * 'guestfwd': [ * { "str": "tcp:10.0.2.1:4600-chardev:charchannel0" }, * { "str": "...."}, * ] * * guestfwd=tcp:10.0.2.1:4600-chardev:charchannel0,guestfwd=... */ static int virQEMUBuildCommandLineJSONArrayObjectsStr(const char *key, virJSONValuePtr array, virBufferPtr buf, const char *skipKey G_GNUC_UNUSED, bool onOff G_GNUC_UNUSED) { g_auto(virBuffer) tmp = VIR_BUFFER_INITIALIZER; size_t i; for (i = 0; i < virJSONValueArraySize(array); i++) { virJSONValuePtr member = virJSONValueArrayGet(array, i); const char *str = virJSONValueObjectGetString(member, "str"); if (!str) return -1; virBufferAsprintf(&tmp, "%s=%s,", key, str); } virBufferAddBuffer(buf, &tmp); return 0; } /* internal iterator to handle nested object formatting */ static int virQEMUBuildCommandLineJSONIterate(const char *key, virJSONValuePtr value, void *opaque) { struct virQEMUCommandLineJSONIteratorData *data = opaque; if (STREQ_NULLABLE(key, data->skipKey)) return 0; if (data->prefix) { g_autofree char *tmpkey = NULL; tmpkey = g_strdup_printf("%s.%s", data->prefix, key); return virQEMUBuildCommandLineJSONRecurse(tmpkey, value, data->buf, data->skipKey, data->onOff, data->arrayFunc, false); } else { return virQEMUBuildCommandLineJSONRecurse(key, value, data->buf, data->skipKey, data->onOff, data->arrayFunc, false); } } static int virQEMUBuildCommandLineJSONRecurse(const char *key, virJSONValuePtr value, virBufferPtr buf, const char *skipKey, bool onOff, virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, bool nested) { struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, onOff, arrayFunc }; virJSONType type = virJSONValueGetType(value); virJSONValuePtr elem; bool tmp; size_t i; if (!key && type != VIR_JSON_TYPE_OBJECT) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("only JSON objects can be top level")); return -1; } switch (type) { case VIR_JSON_TYPE_STRING: virBufferAsprintf(buf, "%s=", key); virQEMUBuildBufferEscapeComma(buf, virJSONValueGetString(value)); virBufferAddLit(buf, ","); break; case VIR_JSON_TYPE_NUMBER: virBufferAsprintf(buf, "%s=%s,", key, virJSONValueGetNumberString(value)); break; case VIR_JSON_TYPE_BOOLEAN: virJSONValueGetBoolean(value, &tmp); if (onOff) { if (tmp) virBufferAsprintf(buf, "%s=on,", key); else virBufferAsprintf(buf, "%s=off,", key); } else { if (tmp) virBufferAsprintf(buf, "%s=yes,", key); else virBufferAsprintf(buf, "%s=no,", key); } break; case VIR_JSON_TYPE_ARRAY: if (nested) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("nested JSON array to commandline conversion is " "not supported")); return -1; } if (!arrayFunc || arrayFunc(key, value, buf, skipKey, onOff) < 0) { /* fallback, treat the array as a non-bitmap, adding the key * for each member */ for (i = 0; i < virJSONValueArraySize(value); i++) { elem = virJSONValueArrayGet((virJSONValuePtr)value, i); /* recurse to avoid duplicating code */ if (virQEMUBuildCommandLineJSONRecurse(key, elem, buf, skipKey, onOff, arrayFunc, true) < 0) return -1; } } break; case VIR_JSON_TYPE_OBJECT: if (virJSONValueObjectForeachKeyValue(value, virQEMUBuildCommandLineJSONIterate, &data) < 0) return -1; break; case VIR_JSON_TYPE_NULL: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("NULL JSON type can't be converted to commandline")); return -1; } return 0; } /** * virQEMUBuildCommandLineJSON: * @value: json object containing the value * @buf: otuput buffer * @skipKey: name of key that will be handled separately by caller * @onOff: Use 'on' and 'off' for boolean values rather than 'yes' and 'no' * @arrayFunc: array formatter function to allow for different syntax * * Formats JSON value object into command line parameters suitable for use with * qemu. * * Returns 0 on success -1 on error. */ int virQEMUBuildCommandLineJSON(virJSONValuePtr value, virBufferPtr buf, const char *skipKey, bool onOff, virQEMUBuildCommandLineJSONArrayFormatFunc array) { if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, onOff, array, false) < 0) return -1; virBufferTrim(buf, ","); return 0; } /** * virQEMUBuildNetdevCommandlineFromJSON: * @props: JSON properties describing a netdev * @rawjson: don't transform to commandline args, but just stringify json * * Converts @props into arguments for -netdev including all the quirks and * differences between the monitor and command line syntax. * * @rawjson is meant for testing of the schema in the xml2argvtest */ char * virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props, bool rawjson) { const char *type = virJSONValueObjectGetString(props, "type"); g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; if (rawjson) return virJSONValueToString(props, false); virBufferAsprintf(&buf, "%s,", type); if (virQEMUBuildCommandLineJSON(props, &buf, "type", true, virQEMUBuildCommandLineJSONArrayObjectsStr) < 0) return NULL; return virBufferContentAndReset(&buf); } static int virQEMUBuildObjectCommandlineFromJSONInternal(virBufferPtr buf, const char *type, const char *alias, virJSONValuePtr props) { if (!type || !alias) { virReportError(VIR_ERR_INTERNAL_ERROR, _("missing 'type'(%s) or 'alias'(%s) field of QOM 'object'"), NULLSTR(type), NULLSTR(alias)); return -1; } virBufferAsprintf(buf, "%s,id=%s", type, alias); if (props) { virBufferAddLit(buf, ","); if (virQEMUBuildCommandLineJSON(props, buf, NULL, false, virQEMUBuildCommandLineJSONArrayBitmap) < 0) return -1; } return 0; } int virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf, virJSONValuePtr objprops) { const char *type = virJSONValueObjectGetString(objprops, "qom-type"); const char *alias = virJSONValueObjectGetString(objprops, "id"); virJSONValuePtr props = virJSONValueObjectGetObject(objprops, "props"); return virQEMUBuildObjectCommandlineFromJSONInternal(buf, type, alias, props); } char * virQEMUBuildDriveCommandlineFromJSON(virJSONValuePtr srcdef) { virBuffer buf = VIR_BUFFER_INITIALIZER; char *ret = NULL; if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL, false, virQEMUBuildCommandLineJSONArrayNumbered) < 0) goto cleanup; ret = virBufferContentAndReset(&buf); cleanup: virBufferFreeAndReset(&buf); return ret; } /** * virQEMUBuildBufferEscapeComma: * @buf: buffer to append the escaped string * @str: the string to escape * * qemu requires that any values passed on the command line which contain * a ',' must escape it using an extra ',' as the escape character */ void virQEMUBuildBufferEscapeComma(virBufferPtr buf, const char *str) { virBufferEscape(buf, ',', ",", "%s", str); } /** * virQEMUBuildQemuImgKeySecretOpts: * @buf: buffer to build the string into * @encinfo: pointer to encryption info * @alias: alias to use * * Generate the string for id=$alias and any encryption options for * into the buffer. * * Important note, a trailing comma (",") is built into the return since * it's expected other arguments are appended after the id=$alias string. * So either turn something like: * * "key-secret=$alias," * * or * "key-secret=$alias,cipher-alg=twofish-256,cipher-mode=cbc, * hash-alg=sha256,ivgen-alg=plain64,igven-hash-alg=sha256," * */ void virQEMUBuildQemuImgKeySecretOpts(virBufferPtr buf, virStorageEncryptionInfoDefPtr encinfo, const char *alias) { virBufferAsprintf(buf, "key-secret=%s,", alias); if (!encinfo->cipher_name) return; virBufferAddLit(buf, "cipher-alg="); virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_name); virBufferAsprintf(buf, "-%u,", encinfo->cipher_size); if (encinfo->cipher_mode) { virBufferAddLit(buf, "cipher-mode="); virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_mode); virBufferAddLit(buf, ","); } if (encinfo->cipher_hash) { virBufferAddLit(buf, "hash-alg="); virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_hash); virBufferAddLit(buf, ","); } if (!encinfo->ivgen_name) return; virBufferAddLit(buf, "ivgen-alg="); virQEMUBuildBufferEscapeComma(buf, encinfo->ivgen_name); virBufferAddLit(buf, ","); if (encinfo->ivgen_hash) { virBufferAddLit(buf, "ivgen-hash-alg="); virQEMUBuildBufferEscapeComma(buf, encinfo->ivgen_hash); virBufferAddLit(buf, ","); } }