This adds two new inject commands: inject-vmport-reboot inject-vmport-halt
And three guest info commands: vmport-guestinfo-set vmport-guestinfo-get query-vmport-guestinfo More details in qmp-commands.hx Signed-off-by: Don Slutz <dsl...@verizon.com> --- hw/misc/vmport_rpc.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi-schema.json | 95 ++++++++++++++++++ qmp-commands.hx | 141 ++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) diff --git a/hw/misc/vmport_rpc.c b/hw/misc/vmport_rpc.c index e12f912..74cc95e 100644 --- a/hw/misc/vmport_rpc.c +++ b/hw/misc/vmport_rpc.c @@ -218,6 +218,56 @@ typedef struct { uint32_t edi; } vregs; +/* + * Run func() for every VMPortRpc device, traverse the tree for + * everything else. Note: This routine expects that opaque is a + * VMPortRpcFind pointer and not NULL. + */ +static int find_VMPortRpc_device(Object *obj, void *opaque) +{ + VMPortRpcFind *find = opaque; + Object *dev; + VMPortRpcState *s; + + if (find->found) { + return 0; + } + dev = object_dynamic_cast(obj, TYPE_VMPORT_RPC); + s = (VMPortRpcState *)dev; + + if (!s) { + /* Container, traverse it for children */ + return object_child_foreach(obj, find_VMPortRpc_device, opaque); + } + + find->found++; + find->rc = find->func(s, find->arg); + + return 0; +} + +/* + * Loop through all dynamically created VMPortRpc devices and call + * func() for each instance. + */ +static int foreach_dynamic_VMPortRpc_device(FindVMPortRpcDeviceFunc *func, + void *arg) +{ + VMPortRpcFind find = { + .func = func, + .arg = arg, + }; + + /* Loop through all VMPortRpc devices that were spawened outside + * the machine */ + find_VMPortRpc_device(qdev_get_machine(), &find); + if (find.found) { + return find.rc; + } else { + return VMPORT_DEVICE_NOT_FOUND; + } +} + #ifdef VMPORT_RPC_DEBUG /* * Add helper function for traceing. This routine will convert @@ -465,6 +515,26 @@ static int get_guestinfo(VMPortRpcState *s, return GUESTINFO_NOTFOUND; } +static int get_qmp_guestinfo(VMPortRpcState *s, + unsigned int a_key_len, char *a_info_key, + unsigned int *a_value_len, void **a_value_data) +{ + unsigned int i; + + for (i = 0; i < s->used_guestinfo; i++) { + if (s->guestinfo[i] && + (s->guestinfo[i]->key_len == a_key_len) && + (memcmp(a_info_key, s->guestinfo[i]->key_data, + s->guestinfo[i]->key_len) == 0)) { + *a_value_len = s->guestinfo[i]->val_len; + *a_value_data = s->guestinfo[i]->val_data; + return 0; + } + } + + return GUESTINFO_NOTFOUND; +} + static int set_guestinfo(VMPortRpcState *s, int a_key_len, unsigned int a_val_len, char *a_info_key, char *val) { @@ -864,6 +934,210 @@ static uint32_t vmport_rpc_ioport_read(void *opaque, uint32_t addr) return ur.data[0]; } +static int find_send(VMPortRpcState *s, void *arg) +{ + return ctrl_send(s, arg); +} + +static void convert_local_rc(Error **errp, int rc) +{ + switch (rc) { + case 0: + break; + case VMPORT_DEVICE_NOT_FOUND: + error_set(errp, QERR_DEVICE_NOT_FOUND, TYPE_VMPORT_RPC); + break; + case SEND_NOT_OPEN: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "VMWare rpc not open"); + break; + case SEND_SKIPPED: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "VMWare rpc send skipped"); + break; + case SEND_TRUCATED: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "VMWare rpc send trucated"); + break; + case SEND_NO_MEMORY: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare rpc send out of memory"); + break; + case GUESTINFO_NOTFOUND: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare guestinfo not found"); + break; + case GUESTINFO_VALTOOLONG: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare guestinfo value too long"); + break; + case GUESTINFO_KEYTOOLONG: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare guestinfo key too long"); + break; + case GUESTINFO_TOOMANYKEYS: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare guestinfo too many keys"); + break; + case GUESTINFO_NO_MEMORY: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare guestinfo out of memory"); + break; + default: + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "VMWare rpc send rc=%d unknown", rc); + break; + } +} + +void qmp_inject_vmport_reboot(Error **errp) +{ + int rc = foreach_dynamic_VMPortRpc_device(find_send, (void *)"OS_Reboot"); + + convert_local_rc(errp, rc); +} + +void qmp_inject_vmport_halt(Error **errp) +{ + int rc = foreach_dynamic_VMPortRpc_device(find_send, (void *)"OS_Halt"); + + convert_local_rc(errp, rc); +} + +typedef struct keyValue { + void *key_data; + void *value_data; + unsigned int key_len; + unsigned int value_len; +} keyValue; + +static int find_set(VMPortRpcState *s, void *arg) +{ + keyValue *key_value = arg; + + return set_guestinfo(s, key_value->key_len, key_value->value_len, + key_value->key_data, key_value->value_data); +} + +static int find_get(VMPortRpcState *s, void *arg) +{ + keyValue *key_value = arg; + + return get_qmp_guestinfo(s, key_value->key_len, key_value->key_data, + &key_value->value_len, &key_value->value_data); +} + +void qmp_vmport_guestinfo_set(const char *key, const char *value, + bool has_format, enum DataFormat format, + Error **errp) +{ + int rc; + keyValue key_value; + + if (strncmp(key, "guestinfo.", strlen("guestinfo.")) == 0) { + key_value.key_data = (void *)(key + strlen("guestinfo.")); + key_value.key_len = strlen(key) - strlen("guestinfo."); + } else { + key_value.key_data = (void *)key; + key_value.key_len = strlen(key); + } + if (has_format && (format == DATA_FORMAT_BASE64)) { + gsize write_count; + + key_value.value_data = g_base64_decode(value, &write_count); + key_value.value_len = write_count; + } else { + key_value.value_data = (void *)value; + key_value.value_len = strlen(value); + } + + rc = foreach_dynamic_VMPortRpc_device(find_set, (void *)&key_value); + + if (key_value.value_data != value) { + g_free(key_value.value_data); + } + + if (rc) { + convert_local_rc(errp, rc); + return; + } +} + +char *qmp_vmport_guestinfo_get(const char *key, + bool has_format, enum DataFormat format, + Error **errp) +{ + int rc; + keyValue key_value; + char *value; + + if (strncmp(key, "guestinfo.", strlen("guestinfo.")) == 0) { + key_value.key_data = (void *)(key + strlen("guestinfo.")); + key_value.key_len = strlen(key) - strlen("guestinfo."); + } else { + key_value.key_data = (void *)key; + key_value.key_len = strlen(key); + } + + rc = foreach_dynamic_VMPortRpc_device(find_get, (void *)&key_value); + if (rc) { + convert_local_rc(errp, rc); + return NULL; + } + + if (has_format && (format == DATA_FORMAT_BASE64)) { + value = g_base64_encode(key_value.value_data, key_value.value_len); + } else { + /* + * FIXME should check for complete, valid UTF-8 characters. + * Invalid sequences should be replaced by a suitable + * replacement character. + */ + value = g_malloc(key_value.value_len + 1); + memcpy(value, key_value.value_data, key_value.value_len); + value[key_value.value_len] = 0; + } + + return value; +} + +static int find_list(VMPortRpcState *s, void *arg) +{ + VmportGuestInfoList **keys = arg; + unsigned int i; + + for (i = 0; i < s->used_guestinfo; i++) { + if (s->guestinfo[i] && s->guestinfo[i]->key_len) { + VmportGuestInfoList *info = g_malloc0(sizeof(*info)); + + info->value = g_malloc0(sizeof(*info->value)); +#ifdef VMPORT_SHORT + info->value->key = g_strndup(s->guestinfo[i]->key_data, + s->guestinfo[i]->key_len); +#else + info->value->key = g_strdup_printf("guestinfo.%.*s", + (int)s->guestinfo[i]->key_len, + s->guestinfo[i]->key_data); +#endif + + info->next = *keys; + *keys = info; + } + } + + return 0; +} + +VmportGuestInfoList *qmp_query_vmport_guestinfo(Error **errp) +{ + VmportGuestInfoList *keys = NULL; + int rc = foreach_dynamic_VMPortRpc_device(find_list, (void *)&keys); + + if (rc) { + convert_local_rc(errp, rc); + } + + return keys; +} + + static void vmport_rpc_reset(DeviceState *d) { VMPortRpcState *s = VMPORT_RPC(d); @@ -871,6 +1145,7 @@ static void vmport_rpc_reset(DeviceState *d) s->reset_time = 14; s->build_number_value = 0; s->build_number_time = 0; + memset(&s->chans, 0, sizeof(s->chans)); } static void vmport_rpc_realizefn(DeviceState *dev, Error **errp) diff --git a/qapi-schema.json b/qapi-schema.json index e16f8eb..6c3146f 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1271,6 +1271,101 @@ { 'command': 'inject-nmi' } ## +# @inject-vmport-reboot: +# +# Injects a VMWare Tools reboot to the guest. +# +# Returns: If successful, nothing +# +# Since: 2.3 +# +## +{ 'command': 'inject-vmport-reboot' } + +## +# @inject-vmport-halt: +# +# Injects a VMWare Tools halt to the guest. +# +# Returns: If successful, nothing +# +# Since: 2.3 +# +## +{ 'command': 'inject-vmport-halt' } + +## +# @vmport-guestinfo-set: +# +# Set a VMWare Tools guestinfo key to a value +# +# @key: the key to set +# +# @value: The data to set the key to +# +# @format: #optional value encoding (default 'utf8'). +# - base64: value must be base64 encoded text. Its binary +# decoding gets set. +# Bug: invalid base64 is currently not rejected. +# Whitespace *is* invalid. +# - utf8: value's UTF-8 encoding is written +# - value itself is always Unicode regardless of format, like +# any other string. +# +# Returns: Nothing on success +# +# Since: 2.3 +## +{ 'command': 'vmport-guestinfo-set', + 'data': {'key': 'str', 'value': 'str', + '*format': 'DataFormat'} } + +## +# @vmport-guestinfo-get: +# +# Get a VMWare Tools guestinfo value for a key +# +# @key: the key to get +# +# @format: #optional data encoding (default 'utf8'). +# - base64: the value is returned in base64 encoding. +# - utf8: the value is interpreted as UTF-8. +# Bug: can screw up when the buffer contains invalid UTF-8 +# sequences, NUL characters. +# - The return value is always Unicode regardless of format, +# like any other string. +# +# Returns: value for the guest info key +# +# Since: 2.3 +## +{ 'command': 'vmport-guestinfo-get', + 'data': {'key': 'str', '*format': 'DataFormat'}, + 'returns': 'str' } + +## +# @VmportGuestInfo: +# +# Information about a single VMWare Tools guestinfo +# +# @key: The known key +# +# Since: 2.3 +## +{ 'type': 'VmportGuestInfo', 'data': {'key': 'str'} } + +## +# @query-vmport-guestinfo: +# +# Returns information about VMWare Tools guestinfo +# +# Returns: a list of @VmportGuestInfo +# +# Since: 2.3 +## +{ 'command': 'query-vmport-guestinfo', 'returns': ['VmportGuestInfo'] } + +## # @set_link: # # Sets the link status of a virtual network adapter. diff --git a/qmp-commands.hx b/qmp-commands.hx index c5f16dd..0aa11df 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -491,6 +491,147 @@ Note: inject-nmi fails when the guest doesn't support injecting. EQMP { + .name = "inject-vmport-reboot", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_inject_vmport_reboot, + }, + +SQMP +inject-vmport-reboot +---------- + +Inject a VMWare Tools reboot to the guest. + +Arguments: None. + +Example: + +-> { "execute": "inject-vmport-reboot" } +<- { "return": {} } + +Note: inject-vmport-reboot fails when the guest doesn't support injecting. + +EQMP + + { + .name = "inject-vmport-halt", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_inject_vmport_halt, + }, + +SQMP +inject-vmport-halt +---------- + +Inject a VMWare Tools halt to the guest. + +Arguments: None. + +Example: + +-> { "execute": "inject-vmport-halt" } +<- { "return": {} } + +Note: inject-vmport-halt fails when the guest doesn't support injecting. + +EQMP + + { + .name = "vmport-guestinfo-set", + .args_type = "key:s,value:s,format:s?", + .mhandler.cmd_new = qmp_marshal_input_vmport_guestinfo_set, + }, + +SQMP +vmport-guestinfo-set +---------- + +Set a VMWare Tools guestinfo key to a value + +Arguments: + +- "key": the key to set (json-string) +- "value": data to write (json-string) +- "format": data format (json-string, optional) + - Possible values: "utf8" (default), "base64" + Bug: invalid base64 is currently not rejected. + Whitespace *is* invalid. + +Example: + +-> { "execute": "vmport-guestinfo-set", + "arguments": { "key": "foo", + "value": "abcdefgh", + "format": "utf8" } } +<- { "return": {} } + +EQMP + + { + .name = "vmport-guestinfo-get", + .args_type = "key:s,format:s?", + .mhandler.cmd_new = qmp_marshal_input_vmport_guestinfo_get, + }, + +SQMP +vmport-guestinfo-get +---------- + +Get a VMWare Tools guestinfo value for a key + +Arguments: + +- "key": the key to get (json-string) +- "format": data format (json-string, optional) + - Possible values: "utf8" (default), "base64" + - Naturally, format "utf8" works only when the ring buffer + contains valid UTF-8 text. Invalid UTF-8 sequences get + replaced. Bug: replacement doesn't work. Bug: can screw + up on encountering NUL characters. + +Example: + +-> { "execute": "vmport-guestinfo-get", + "arguments": { "key": "foo", + "format": "utf8" } } +<- {"return": "abcdefgh"} + + +EQMP + + { + .name = "query-vmport-guestinfo", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_vmport_guestinfo, + }, + +SQMP +query-vmport-guestinfo +---------- + +Returns information about VMWare Tools guestinfo. The returned value is a json-array +of all keys. + +Example: + +-> { "execute": "query-vmport-guestinfo" } +<- { + "return": [ + { + "key": "ip", + }, + { + "key": "foo", + }, + { + "key": "long", + } + ] + } + +EQMP + + { .name = "ringbuf-write", .args_type = "device:s,data:s,format:s?", .mhandler.cmd_new = qmp_marshal_input_ringbuf_write, -- 1.8.4