This adds one new inject command: inject-vmport-action
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 | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi-schema.json | 90 +++++++++++++++++ qmp-commands.hx | 120 +++++++++++++++++++++++ 3 files changed, 479 insertions(+) diff --git a/hw/misc/vmport_rpc.c b/hw/misc/vmport_rpc.c index 927c0bf..0ba3319 100644 --- a/hw/misc/vmport_rpc.c +++ b/hw/misc/vmport_rpc.c @@ -215,6 +215,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_vmport_rpc_device(FindVMPortRpcDeviceFunc *func, + void *arg) +{ + VMPortRpcFind find = { + .func = func, + .arg = arg, + }; + + /* Loop through all VMPortRpc devices that were spawned 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 tracing. This routine will convert @@ -464,6 +514,23 @@ 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) +{ + gpointer key = g_strndup(a_info_key, a_key_len); + guestinfo_t *gi = (guestinfo_t *)g_hash_table_lookup(s->guestinfo, key); + + g_free(key); + if (gi) { + *a_value_len = gi->val_len; + *a_value_data = gi->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) { @@ -851,6 +918,208 @@ static uint32_t vmport_rpc_ioport_read(void *opaque, uint32_t addr) return ur.data[0]; } +static int vmport_rpc_find_send(VMPortRpcState *s, void *arg) +{ + return vmport_rpc_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_setg(errp, "VMWare rpc not open"); + break; + case SEND_SKIPPED: + error_setg(errp, "VMWare rpc send skipped"); + break; + case SEND_TRUCATED: + error_setg(errp, "VMWare rpc send trucated"); + break; + case SEND_NO_MEMORY: + error_setg(errp, "VMWare rpc send out of memory"); + break; + case GUESTINFO_NOTFOUND: + error_setg(errp, "VMWare guestinfo not found"); + break; + case GUESTINFO_VALTOOLONG: + error_setg(errp, "VMWare guestinfo value too long"); + break; + case GUESTINFO_KEYTOOLONG: + error_setg(errp, "VMWare guestinfo key too long"); + break; + case GUESTINFO_TOOMANYKEYS: + error_setg(errp, "VMWare guestinfo too many keys"); + break; + case GUESTINFO_NO_MEMORY: + error_setg(errp, "VMWare guestinfo out of memory"); + break; + default: + error_setg(errp, "VMWare rpc send rc=%d unknown", rc); + break; + } +} + +void qmp_inject_vmport_action(enum VmportAction action, Error **errp) +{ + int rc; + + switch (action) { + case VMPORT_ACTION_REBOOT: + rc = foreach_dynamic_vmport_rpc_device(vmport_rpc_find_send, + (void *)"OS_Reboot"); + break; + case VMPORT_ACTION_HALT: + rc = foreach_dynamic_vmport_rpc_device(vmport_rpc_find_send, + (void *)"OS_Halt"); + break; + case VMPORT_ACTION_MAX: + assert(action != VMPORT_ACTION_MAX); + rc = 0; /* Should be impossible to get here. */ + break; + } + 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_vmport_rpc_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_vmport_rpc_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 void vmport_rpc_find_list_one(gpointer key, gpointer value, + gpointer opaque) +{ + VmportGuestInfoList **keys = opaque; + VmportGuestInfoList *info = g_malloc0(sizeof(*info)); + char *ckey = key; + + info->value = g_malloc0(sizeof(*info->value)); +#ifdef VMPORT_SHORT + info->value->key = g_strdup(ckey); +#else + info->value->key = g_strdup_printf("guestinfo.%s", ckey); +#endif + info->next = *keys; + *keys = info; +} + +static int vmport_rpc_find_list(VMPortRpcState *s, void *arg) +{ + g_hash_table_foreach(s->guestinfo, vmport_rpc_find_list_one, arg); + return 0; +} + +VmportGuestInfoList *qmp_query_vmport_guestinfo(Error **errp) +{ + VmportGuestInfoList *keys = NULL; + int rc = foreach_dynamic_vmport_rpc_device(vmport_rpc_find_list, + (void *)&keys); + + if (rc) { + convert_local_rc(errp, rc); + } + + return keys; +} + + static void vmport_rpc_reset(DeviceState *d) { unsigned int i; diff --git a/qapi-schema.json b/qapi-schema.json index 2b3e275..29ffaa1 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1271,6 +1271,96 @@ { 'command': 'inject-nmi' } ## +# @VmportAction: +# +# An enumeration of actions that can be requested via vmport RPC. +# +# @reboot: Ask the guest via VMware tools to reboot +# +# @halt: Ask the guest via VMware tools to halt +# +# Since: 2.3 +## +{ 'enum': 'VmportAction', + 'data': [ 'reboot', 'halt' ] } + +## +# @inject-vmport-action: +# +# Injects a VMWare Tools action to the guest. +# +# Returns: If successful, nothing +# +# Since: 2.3 +# +## +{ 'command': 'inject-vmport-action', + 'data': {'action': 'VmportAction'} } + +## +# @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 is assumed to be base64 encoded text. Its binary +# decoding gets set. +# - utf8: value's UTF-8 encoding is used to set. +# +# 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. +# +# 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 0663924..190903c 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -491,6 +491,126 @@ Note: inject-nmi fails when the guest doesn't support injecting. EQMP { + .name = "inject-vmport-action", + .args_type = "action:s", + .mhandler.cmd_new = qmp_marshal_input_inject_vmport_action, + }, + +SQMP +inject-vmport-action +---------- + +Inject a VMWare Tools action to the guest. + +Arguments: + +- "action": vmport action (json-string) + - Possible values: "reboot", "halt" + +Example: + +-> { "execute": "inject-vmport-action", + "arguments": { "action": "reboot" } } +<- { "return": {} } + +Note: inject-vmport-action 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" + +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