[libvirt] [PATCH 0/5] Implement qemu-ga 'guest-get-users'

This set of patches adds new API and an implementation for getting the active users for a domain via the guest agent. There is only an implementation for the qemu driver. I've implemented the remote protocol and added support in virsh (new command 'guestusers'). A lot of the implementation was modelled on the implementation of guest-get-fsinfo, which also returns an array of info structures. Since this is my first time adding API or dealing with libvirt RPC stuff, I anticipate several rounds of the series. This series addresses https://bugzilla.redhat.com/show_bug.cgi?id=1704761 Jonathon Jongsma (5): lib: add API to query info about logged-in users remote: implement remote protocol for virDomainGetGuestUsers virsh: add command 'guestusers' implementing VirDomainGetGuestUsers qemu_agent: add helper for getting guest users qemu: implement virDomainGetGuestUsers include/libvirt/libvirt-domain.h | 18 ++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 62 ++++++++++++++ src/libvirt_public.syms | 5 ++ src/qemu/qemu_agent.c | 92 ++++++++++++++++++++ src/qemu/qemu_agent.h | 2 + src/qemu/qemu_driver.c | 38 +++++++++ src/remote/remote_daemon_dispatch.c | 89 ++++++++++++++++++++ src/remote/remote_driver.c | 82 +++++++++++++++++- src/remote/remote_protocol.x | 26 +++++- src/remote_protocol-structs | 17 ++++ tests/qemuagenttest.c | 125 ++++++++++++++++++++++++++++ tools/virsh-domain.c | 76 +++++++++++++++++ tools/virsh.pod | 4 + 14 files changed, 640 insertions(+), 2 deletions(-) -- 2.20.1

Add API for querying logged-in users from a domain implemented via guest agent. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-domain.h | 18 ++++++++++ src/driver-hypervisor.h | 6 ++++ src/libvirt-domain.c | 62 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 91 insertions(+) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 2dbd74d4f3..82dbbd3fc5 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -4896,4 +4896,22 @@ int virDomainGetLaunchSecurityInfo(virDomainPtr domain, int *nparams, unsigned int flags); +/** + * virDomainUserInfo: + * + * The data structure containing informationa bout logged-in users within a + * guest + */ +typedef struct _virDomainUserInfo virDomainUserInfo; +typedef virDomainUserInfo *virDomainUserInfoPtr; +struct _virDomainUserInfo { + char *user; /* username */ + char *domain; /* login domain (windows only) */ + unsigned long long loginTime; /* timestamp of login for this user in ms since epoch */ +}; +int virDomainGetGuestUsers(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags); +void virDomainUserInfoFree(virDomainUserInfoPtr info); + #endif /* LIBVIRT_DOMAIN_H */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index b15aaa36bc..0ef7257ace 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1327,6 +1327,11 @@ typedef int int *nparams, unsigned int flags); +typedef int +(*virDrvDomainGetGuestUsers)(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags); + typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1579,4 +1584,5 @@ struct _virHypervisorDriver { virDrvConnectBaselineHypervisorCPU connectBaselineHypervisorCPU; virDrvNodeGetSEVInfo nodeGetSEVInfo; virDrvDomainGetLaunchSecurityInfo domainGetLaunchSecurityInfo; + virDrvDomainGetGuestUsers domainGetGuestUsers; }; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 3d12e7c125..80faa08758 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -12199,6 +12199,68 @@ virDomainSetVcpu(virDomainPtr domain, return -1; } +/** + * virDomainGetGuestUsers: + * @domain: pointer to domain object + * @info: a pointer to a variable to store an array of user info + * @flags: currently unused, callers shall pass 0 + * + * Queries the guest agent for the list of currently active users on the + * guest. The reported data depends on the guest agent implementation. + * + * This API requires the VM to run. The caller is responsible for calling + * virTypedParamsFree to free memory returned in @params. + * + * Returns the number of returned users, or -1 in case of error. + * On success, the array of the information is stored into @info. The caller is + * responsible for calling virDomainUserInfoFree() on each array element, then + * calling free() on @info. On error, @info is set to NULL. + */ +int +virDomainGetGuestUsers(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags) +{ + virResetLastError(); + + virCheckDomainReturn(domain, -1); + virCheckReadOnlyGoto(domain->conn->flags, error); + + virCheckNonNullArgGoto(info, error); + + if (domain->conn->driver->domainGetGuestUsers) { + int ret; + ret = domain->conn->driver->domainGetGuestUsers(domain, info, + flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(domain->conn); + return -1; +} + +/** + * virDomainUserInfoFree: + * @info: pointer to a UserInfo object + * + * Frees all the memory occupied by @info. + */ +void +virDomainUserInfoFree(virDomainUserInfoPtr info) +{ + if (!info) + return; + + VIR_FREE(info->user); + VIR_FREE(info->domain); + + VIR_FREE(info); +} /** * virDomainSetBlockThreshold: diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 18500ec8b2..7d0e3c7849 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -836,4 +836,9 @@ LIBVIRT_5.5.0 { virNetworkPortSetParameters; } LIBVIRT_5.2.0; +LIBVIRT_5.6.0 { + global: + virDomainGetGuestUsers; + virDomainUserInfoFree; +} LIBVIRT_5.5.0; # .... define new API here using predicted next version number .... -- 2.20.1

On Wed, Jul 10, 2019 at 03:07:43PM -0500, Jonathon Jongsma wrote:
Add API for querying logged-in users from a domain implemented via guest agent.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-domain.h | 18 ++++++++++ src/driver-hypervisor.h | 6 ++++ src/libvirt-domain.c | 62 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 91 insertions(+)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 2dbd74d4f3..82dbbd3fc5 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -4896,4 +4896,22 @@ int virDomainGetLaunchSecurityInfo(virDomainPtr domain, int *nparams, unsigned int flags);
+/** + * virDomainUserInfo: + * + * The data structure containing informationa bout logged-in users within a + * guest + */ +typedef struct _virDomainUserInfo virDomainUserInfo; +typedef virDomainUserInfo *virDomainUserInfoPtr; +struct _virDomainUserInfo { + char *user; /* username */ + char *domain; /* login domain (windows only) */ + unsigned long long loginTime; /* timestamp of login for this user in ms since epoch */ +}; +int virDomainGetGuestUsers(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags);
I can easily imagine QEMU returning more info per user in future, so using a struct is not futureproof design. We should use the virTypedParameter approach instead here. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Thu, Jul 11, 2019 at 12:09:23 +0100, Daniel Berrange wrote:
On Wed, Jul 10, 2019 at 03:07:43PM -0500, Jonathon Jongsma wrote:
Add API for querying logged-in users from a domain implemented via guest agent.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-domain.h | 18 ++++++++++ src/driver-hypervisor.h | 6 ++++ src/libvirt-domain.c | 62 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 91 insertions(+)
[...]
+int virDomainGetGuestUsers(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags);
I can easily imagine QEMU returning more info per user in future, so using a struct is not futureproof design.
We should use the virTypedParameter approach instead here.
In addition to that, once we go with typed parameters, we can make the API more universal similarly to virDomainListGetStats. Working on one VM only in this case, but returning all the possible queryable stuff from the guest agent, so that we don't have to add APIs for every single thing an user would want to query.

On Thu, 2019-07-11 at 13:42 +0200, Peter Krempa wrote:
On Thu, Jul 11, 2019 at 12:09:23 +0100, Daniel Berrange wrote:
On Wed, Jul 10, 2019 at 03:07:43PM -0500, Jonathon Jongsma wrote:
Add API for querying logged-in users from a domain implemented via guest agent.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- include/libvirt/libvirt-domain.h | 18 ++++++++++ src/driver-hypervisor.h | 6 ++++ src/libvirt-domain.c | 62 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 91 insertions(+)
[...]
+int virDomainGetGuestUsers(virDomainPtr domain, + virDomainUserInfoPtr **info, + unsigned int flags);
I can easily imagine QEMU returning more info per user in future, so using a struct is not futureproof design.
We should use the virTypedParameter approach instead here.
In addition to that, once we go with typed parameters, we can make the API more universal similarly to virDomainListGetStats. Working on one VM only in this case, but returning all the possible queryable stuff from the guest agent, so that we don't have to add APIs for every single thing an user would want to query.
Good points, thanks for the feedback. I'll have to think a bit more about a single API for aggregated stats. I think that could get a bit ugly, but we'll see. Are you suggesting folding the existing agent queries (e.g. fsinfo, vcpuinfo, etc) into this same aggregated API? Jonathon

Add daemon and client code to serialize/deserialize virDomainUserInfo Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/remote/remote_daemon_dispatch.c | 89 +++++++++++++++++++++++++++++ src/remote/remote_driver.c | 82 +++++++++++++++++++++++++- src/remote/remote_protocol.x | 26 ++++++++- src/remote_protocol-structs | 17 ++++++ 4 files changed, 212 insertions(+), 2 deletions(-) diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c index 856c5e48e7..05c4b11b11 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -7465,3 +7465,92 @@ remoteSerializeDomainDiskErrors(virDomainDiskErrorPtr errors, } return -1; } + +static int +remoteDispatchDomainGetGuestUsers(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_guest_users_args *args, + remote_domain_get_guest_users_ret *ret) +{ + int rv = -1; + size_t i; + struct daemonClientPrivate *priv = virNetServerClientGetPrivateData(client); + virDomainUserInfoPtr *info = NULL; + virDomainPtr dom = NULL; + remote_domain_userinfo *dst; + int ninfo = 0; + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + if ((ninfo = virDomainGetGuestUsers(dom, &info, args->flags)) < 0) + goto cleanup; + + if (ninfo > REMOTE_DOMAIN_GUEST_USERS_MAX) { + virReportError(VIR_ERR_RPC, + _("Too many users in userinfo: %d for limit %d"), + ninfo, REMOTE_DOMAIN_GUEST_USERS_MAX); + goto cleanup; + } + + if (ninfo) { + if (VIR_ALLOC_N(ret->info.info_val, ninfo) < 0) + goto cleanup; + + ret->info.info_len = ninfo; + + for (i = 0; i < ninfo; i++) { + dst = &ret->info.info_val[i]; + if (VIR_STRDUP(dst->user, info[i]->user) < 0) + goto cleanup; + + if (info[i]->domain) { + if (VIR_ALLOC(*dst->domain) < 0) + goto cleanup; + if (VIR_STRDUP(*dst->domain, info[i]->domain) < 0) + goto cleanup; + } + + dst->login_time = info[i]->loginTime; + + } + + } else { + ret->info.info_len = 0; + ret->info.info_val = NULL; + } + + ret->ret = ninfo; + + rv = 0; + + cleanup: + if (rv < 0) { + virNetMessageSaveError(rerr); + + if (ret->info.info_val && ninfo > 0) { + for (i = 0; i < ninfo; i++) { + dst = &ret->info.info_val[i]; + VIR_FREE(dst->user); + if(dst->domain) + VIR_FREE(*dst->domain); + VIR_FREE(dst->domain); + } + VIR_FREE(ret->info.info_val); + } + } + virObjectUnref(dom); + if (ninfo >= 0) + for (i = 0; i < ninfo; i++) + virDomainUserInfoFree(info[i]); + VIR_FREE(info); + + return rv; +} diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 925ada1cac..86e2071d3b 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -8167,6 +8167,85 @@ remoteNetworkPortGetParameters(virNetworkPortPtr port, return rv; } +static int +remoteDomainGetGuestUsers(virDomainPtr dom, + virDomainUserInfoPtr **info, + unsigned int flags) +{ + int rv = -1; + size_t i; + struct private_data *priv = dom->conn->privateData; + remote_domain_get_guest_users_args args; + remote_domain_get_guest_users_ret ret; + remote_domain_userinfo *src; + virDomainUserInfoPtr *info_ret = NULL; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, dom); + + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + + if (call(dom->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_GUEST_USERS, + (xdrproc_t)xdr_remote_domain_get_guest_users_args, (char *)&args, + (xdrproc_t)xdr_remote_domain_get_guest_users_ret, (char *)&ret) == -1) + goto done; + + if (ret.info.info_len > REMOTE_DOMAIN_GUEST_USERS_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Too many users in userinfo: %d for limit %d"), + ret.info.info_len, REMOTE_DOMAIN_GUEST_USERS_MAX); + goto cleanup; + } + + if (info) { + if (!ret.info.info_len) { + *info = NULL; + rv = ret.ret; + goto cleanup; + } + + if (VIR_ALLOC_N(info_ret, ret.info.info_len) < 0) + goto cleanup; + + for (i = 0; i < ret.info.info_len; i++) { + src = &ret.info.info_val[i]; + + if (VIR_ALLOC(info_ret[i]) < 0) + goto cleanup; + + if (VIR_STRDUP(info_ret[i]->user, src->user) < 0) + goto cleanup; + + if (src->domain) { + if (VIR_STRDUP(info_ret[i]->domain, *src->domain) < 0) + goto cleanup; + } + + info_ret[i]->loginTime = src->login_time; + } + + *info = info_ret; + info_ret = NULL; + } + + rv = ret.ret; + + cleanup: + if (info_ret) { + for (i = 0; i < ret.info.info_len; i++) + virDomainUserInfoFree(info_ret[i]); + VIR_FREE(info_ret); + } + xdr_free((xdrproc_t)xdr_remote_domain_get_guest_users_ret, + (char *) &ret); + + done: + remoteDriverUnlock(priv); + return rv; +} /* get_nonnull_domain and get_nonnull_network turn an on-wire * (name, uuid) pair into virDomainPtr or virNetworkPtr object. @@ -8573,7 +8652,8 @@ static virHypervisorDriver hypervisor_driver = { .connectCompareHypervisorCPU = remoteConnectCompareHypervisorCPU, /* 4.4.0 */ .connectBaselineHypervisorCPU = remoteConnectBaselineHypervisorCPU, /* 4.4.0 */ .nodeGetSEVInfo = remoteNodeGetSEVInfo, /* 4.5.0 */ - .domainGetLaunchSecurityInfo = remoteDomainGetLaunchSecurityInfo /* 4.5.0 */ + .domainGetLaunchSecurityInfo = remoteDomainGetLaunchSecurityInfo, /* 4.5.0 */ + .domainGetGuestUsers = remoteDomainGetGuestUsers /* 5.6.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 2e45b5cef0..5f2f350cb0 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -266,6 +266,9 @@ const REMOTE_NODE_SEV_INFO_MAX = 64; /* Upper limit on number of launch security information entries */ const REMOTE_DOMAIN_LAUNCH_SECURITY_INFO_PARAMS_MAX = 64; +/* Upper limit on number of guest user information entries */ +const REMOTE_DOMAIN_GUEST_USERS_MAX = 64; + /* * Upper limit on list of network port parameters */ @@ -3649,6 +3652,21 @@ struct remote_network_port_delete_args { unsigned int flags; }; +struct remote_domain_userinfo { + remote_nonnull_string user; + remote_string domain; + unsigned hyper login_time; +}; + +struct remote_domain_get_guest_users_args { + remote_nonnull_domain dom; + unsigned int flags; +}; + +struct remote_domain_get_guest_users_ret { + remote_domain_userinfo info<REMOTE_DOMAIN_GUEST_USERS_MAX>; + unsigned int ret; +}; /*----- Protocol. -----*/ @@ -6463,5 +6481,11 @@ enum remote_procedure { * @generate: both * @acl: network_port:delete */ - REMOTE_PROC_NETWORK_PORT_DELETE = 410 + REMOTE_PROC_NETWORK_PORT_DELETE = 410, + + /** + * @generate: none + * @acl: domain:write + */ + REMOTE_PROC_DOMAIN_GET_GUEST_USERS = 411 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 2398494520..ee39fb2d0d 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -3043,6 +3043,22 @@ struct remote_network_port_delete_args { remote_nonnull_network_port port; u_int flags; }; +struct remote_domain_userinfo { + remote_nonnull_string user; + remote_string domain; + uint64_t login_time; +}; +struct remote_domain_get_guest_users_args { + remote_nonnull_domain dom; + u_int flags; +}; +struct remote_domain_get_guest_users_ret { + struct { + u_int info_len; + remote_domain_userinfo * info_val; + } info; + u_int ret; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3454,4 +3470,5 @@ enum remote_procedure { REMOTE_PROC_NETWORK_PORT_SET_PARAMETERS = 408, REMOTE_PROC_NETWORK_PORT_GET_XML_DESC = 409, REMOTE_PROC_NETWORK_PORT_DELETE = 410, + REMOTE_PROC_DOMAIN_GET_GUEST_USERS = 411, }; -- 2.20.1

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- tools/virsh-domain.c | 76 ++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 4 +++ 2 files changed, 80 insertions(+) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 828ae30789..b964608987 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -14030,6 +14030,76 @@ cmdDomFSInfo(vshControl *ctl, const vshCmd *cmd) return ret; } +/* + * "guestusers" command + */ +static const vshCmdInfo info_guestusers[] = { + {.name = "help", + .data = N_("query the users logged on in the guest (via agent)") + }, + {.name = "desc", + .data = N_("Use the guest agent to query the list of logged in users from guest's " + "point of view") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_guestusers[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE), + {.name = NULL} +}; + +static bool +cmdGuestusers(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virDomainUserInfoPtr *userinfo = NULL; + int ninfo = 0; + size_t i; + bool ret = false; + vshTablePtr table = NULL; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if ((ninfo = virDomainGetGuestUsers(dom, &userinfo, 0)) < 0) + goto cleanup; + + if (userinfo != NULL) { + table = vshTableNew(_("User"), _("Domain"), _("Login Time"), NULL); + if (!table) + goto cleanup; + + for (i = 0; i < ninfo; i++) { + VIR_AUTOFREE(char *) loginstr = virTimeStringThen(userinfo[i]->loginTime); + if (loginstr == NULL) + goto cleanup; + + if (vshTableRowAppend(table, + userinfo[i]->user, + userinfo[i]->domain ? userinfo[i]->domain : "", + loginstr, + NULL) < 0) + goto cleanup; + } + + vshTablePrintToStdout(table, ctl); + } else { + vshPrintExtra(ctl, _("No active users in the domain")); + } + + ret = true; + +cleanup: + if (ninfo >= 0) { + for (i = 0; i < ninfo; i++) + virDomainUserInfoFree(userinfo[i]); + } + VIR_FREE(userinfo); + virshDomainFree(dom); + return ret; +} + const vshCmdDef domManagementCmds[] = { {.name = "attach-device", .handler = cmdAttachDevice, @@ -14645,5 +14715,11 @@ const vshCmdDef domManagementCmds[] = { .info = info_domblkthreshold, .flags = 0 }, + {.name = "guestusers", + .handler = cmdGuestusers, + .opts = opts_guestusers, + .info = info_guestusers, + .flags = 0 + }, {.name = NULL} }; diff --git a/tools/virsh.pod b/tools/virsh.pod index 5168fa96b6..59cf3d5857 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -3072,6 +3072,10 @@ provided too. The desired operation is then executed on the domain. See B<vcpupin> for information on I<cpulist>. +=item B<guestusers> I<domain> + +Query the list of logged in users from guest's point of view using the guest agent. + =item B<vncdisplay> I<domain> Output the IP address and port number for the VNC display. If the information -- 2.20.1

This function fetches the list of logged-in users from the qemu agent and converts them into a form that can be used internally in libvirt. Also add some basic tests for the function. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_agent.c | 92 +++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 2 + tests/qemuagenttest.c | 125 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 361db299a5..b70d6806cd 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -2240,3 +2240,95 @@ qemuAgentSetUserPassword(qemuAgentPtr mon, VIR_FREE(password64); return ret; } + +int +qemuAgentGetUsers(qemuAgentPtr mon, + virDomainUserInfoPtr **info) +{ + int ret = -1; + size_t i; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr data = NULL; + size_t ndata; + const char *result; + virDomainUserInfoPtr *users = NULL; + + if (!(cmd = qemuAgentMakeCommand("guest-get-users", NULL))) + return -1; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (!(data = virJSONValueObjectGetArray(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-users reply was missing return data")); + goto cleanup; + } + + if (!virJSONValueIsArray(data)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed guest-get-users data array")); + goto cleanup; + } + + ndata = virJSONValueArraySize(data); + + if (VIR_ALLOC_N(users, ndata) < 0) + goto cleanup; + + for (i = 0; i < ndata; i++) { + virJSONValuePtr entry = virJSONValueArrayGet(data, i); + if (VIR_ALLOC(users[i]) < 0) + goto cleanup; + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("array element missing in guest-get-users return " + "value")); + goto cleanup; + } + + if (!(result = virJSONValueObjectGetString(entry, "user"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'user' missing in reply of guest-get-users")); + goto cleanup; + } + + if (VIR_STRDUP(users[i]->user, result) < 0) { + goto cleanup; + } + + /* 'domain' is only present for windows guests */ + if ((result = virJSONValueObjectGetString(entry, "domain"))) { + if (VIR_STRDUP(users[i]->domain, result) < 0) { + goto cleanup; + } + } + + double loginseconds; + if (virJSONValueObjectGetNumberDouble(entry, "login-time", &loginseconds) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'login-time' missing in reply of guest-get-users")); + goto cleanup; + } + /* convert to milliseconds */ + users[i]->loginTime = loginseconds * 1000; + } + + *info = users; + users = NULL; + ret = ndata; + + cleanup: + if (users) { + for (i = 0; i < ndata; i++) { + virDomainUserInfoFree(users[i]); + } + VIR_FREE(users); + } + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index 6ae9fe54da..70797dc894 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -120,3 +120,5 @@ int qemuAgentSetUserPassword(qemuAgentPtr mon, const char *user, const char *password, bool crypted); + +int qemuAgentGetUsers(qemuAgentPtr mon, virDomainUserInfoPtr **info); diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 2f79986207..d97f05102e 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -902,6 +902,130 @@ testQemuAgentGetInterfaces(const void *data) return ret; } +static const char testQemuAgentUsersResponse[] = + "{\"return\": " + " [" + " {\"user\": \"test\"," + " \"login-time\": 1561739203.584038" + " }," + " {\"user\": \"test2\"," + " \"login-time\": 1561739229.190697" + " }" + " ]" + "}"; + +static const char testQemuAgentUsersResponse2[] = + "{\"return\": " + " [" + " {\"user\": \"test\"," + " \"domain\": \"DOMAIN\"," + " \"login-time\": 1561739203.584038" + " }" + " ]" + "}"; + +static int +testQemuAgentUsers(const void *data) +{ + virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data; + qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt); + virDomainUserInfoPtr *userinfo = NULL; + int nusers; + int ret = -1; + int i; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-get-users", + testQemuAgentUsersResponse) < 0) + goto cleanup; + + /* get users */ + if ((nusers = qemuAgentGetUsers(qemuMonitorTestGetAgent(test), + &userinfo)) < 0) + goto cleanup; + + if (nusers != 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected '2' users, got '%d'", nusers); + goto cleanup; + } + if (STRNEQ(userinfo[0]->user, "test")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected user name 'test', got '%s'", userinfo[0]->user); + goto cleanup; + } + if (userinfo[0]->loginTime != 1561739203.584038) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected login time of '1561739203584', got '%llu'", + userinfo[0]->loginTime); + goto cleanup; + } + + if (STRNEQ(userinfo[1]->user, "test2")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected user name 'test2', got '%s'", userinfo[1]->user); + goto cleanup; + } + if (userinfo[1]->loginTime != 1561739229.190697) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected login time of '1561739229190', got '%llu'", + userinfo[1]->loginTime); + goto cleanup; + } + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-get-users", + testQemuAgentUsersResponse2) < 0) + goto cleanup; + + VIR_FREE(userinfo); + /* get users with domain */ + if ((nusers = qemuAgentGetUsers(qemuMonitorTestGetAgent(test), + &userinfo)) < 0) + goto cleanup; + + if (nusers != 1) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected '1' user, got '%d'", nusers); + goto cleanup; + } + + if (STRNEQ(userinfo[0]->user, "test")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected user name 'test', got '%s'", userinfo[0]->user); + goto cleanup; + } + if (userinfo[0]->loginTime != 1561739203.584038) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected login time of '1561739203584', got '%llu'", + userinfo[0]->loginTime); + goto cleanup; + } + if (STRNEQ(userinfo[0]->domain, "DOMAIN")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected domain 'DOMAIN', got '%s'", userinfo[0]->domain); + goto cleanup; + } + ret = 0; + + cleanup: + if (nusers >= 0) { + for (i = 0; i < nusers; i++) + virDomainUserInfoFree(userinfo[i]); + } + VIR_FREE(userinfo); + qemuMonitorTestFree(test); + return ret; +} + + static int mymain(void) { @@ -931,6 +1055,7 @@ mymain(void) DO_TEST(CPU); DO_TEST(ArbitraryCommand); DO_TEST(GetInterfaces); + DO_TEST(Users); DO_TEST(Timeout); /* Timeout should always be called last */ -- 2.20.1

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> --- src/qemu/qemu_driver.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5a75f23981..27fcdd393d 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -22201,6 +22201,43 @@ qemuDomainGetLaunchSecurityInfo(virDomainPtr domain, return ret; } +static int +qemuDomainGetGuestUsers(virDomainPtr dom, + virDomainUserInfoPtr **info, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + virDomainObjPtr vm = NULL; + qemuAgentPtr agent; + int ret = -1; + + virCheckFlags(0, ret); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + if (virDomainGetGuestUsersEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_QUERY) < 0) + goto cleanup; + + if (!qemuDomainAgentAvailable(vm, true)) + goto endjob; + + agent = qemuDomainObjEnterAgent(vm); + ret = qemuAgentGetUsers(agent, info); + qemuDomainObjExitAgent(vm, agent); + + endjob: + qemuDomainObjEndAgentJob(vm); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, .connectURIProbe = qemuConnectURIProbe, @@ -22428,6 +22465,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .connectBaselineHypervisorCPU = qemuConnectBaselineHypervisorCPU, /* 4.4.0 */ .nodeGetSEVInfo = qemuNodeGetSEVInfo, /* 4.5.0 */ .domainGetLaunchSecurityInfo = qemuDomainGetLaunchSecurityInfo, /* 4.5.0 */ + .domainGetGuestUsers = qemuDomainGetGuestUsers, /* 5.6.0 */ }; -- 2.20.1
participants (3)
-
Daniel P. Berrangé
-
Jonathon Jongsma
-
Peter Krempa