All of lore.kernel.org
 help / color / mirror / Atom feed
From: Michael Roth <mdroth@linux.vnet.ibm.com>
To: Vinzenz 'evilissimo' Feenstra <vfeenstr@redhat.com>,
	qemu-devel@nongnu.org
Cc: marcandre.lureau@gmail.com
Subject: Re: [Qemu-devel] [PATCH v3 1/1] qga: Add 'guest-get-users' command
Date: Wed, 12 Apr 2017 15:07:42 -0500	[thread overview]
Message-ID: <149202766263.22939.9361142502968019871@loki> (raw)
In-Reply-To: <20170404055146.8901-2-vfeenstr@redhat.com>

Quoting Vinzenz 'evilissimo' Feenstra (2017-04-04 00:51:46)
> From: Vinzenz Feenstra <vfeenstr@redhat.com>
> 
> A command that will list all currently logged in users, and the time
> since when they are logged in.
> 
> Examples:
> 
> virsh # qemu-agent-command F25 '{ "execute": "guest-get-users" }'
> {"return":[{"login-time":1490622289.903835,"user":"root"}]}
> 
> virsh # qemu-agent-command Win2k12r2 '{ "execute": "guest-get-users" }'
> {"return":[{"login-time":1490351044.670552,"domain":"LADIDA",
> "user":"Administrator"}]}
> 
> Signed-off-by: Vinzenz Feenstra <vfeenstr@redhat.com>
> ---
>  configure             |  2 +-
>  include/glib-compat.h |  5 +++
>  qga/commands-posix.c  | 54 +++++++++++++++++++++++++++++++
>  qga/commands-win32.c  | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  qga/qapi-schema.json  | 24 ++++++++++++++
>  5 files changed, 173 insertions(+), 1 deletion(-)
> 
> diff --git a/configure b/configure
> index d1ce33b..779ebfd 100755
> --- a/configure
> +++ b/configure
> @@ -737,7 +737,7 @@ if test "$mingw32" = "yes" ; then
>    sysconfdir="\${prefix}"
>    local_statedir=
>    confsuffix=""
> -  libs_qga="-lws2_32 -lwinmm -lpowrprof -liphlpapi -lnetapi32 $libs_qga"
> +  libs_qga="-lws2_32 -lwinmm -lpowrprof -lwtsapi32 -liphlpapi -lnetapi32 $libs_qga"
>  fi
> 
>  werror=""
> diff --git a/include/glib-compat.h b/include/glib-compat.h
> index 863c8cf..f8ee9dc 100644
> --- a/include/glib-compat.h
> +++ b/include/glib-compat.h
> @@ -217,6 +217,11 @@ static inline void g_hash_table_add(GHashTable *hash_table, gpointer key)
>  {
>      g_hash_table_replace(hash_table, key, key);
>  }
> +
> +static gboolean g_hash_table_contains(GHashTable *hash_table, gpointer key)
> +{
> +    return g_hash_table_lookup_extended(hash_table, key, NULL, NULL);
> +}
>  #endif
> 
>  #ifndef g_assert_true
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index 73d93eb..3081ee7 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -15,6 +15,7 @@
>  #include <sys/ioctl.h>
>  #include <sys/wait.h>
>  #include <dirent.h>
> +#include <utmpx.h>
>  #include "qga/guest-agent-core.h"
>  #include "qga-qmp-commands.h"
>  #include "qapi/qmp/qerror.h"
> @@ -2515,3 +2516,56 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
>      ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
>  #endif
>  }
> +
> +#ifndef QGA_MICRO_SECOND_TO_SECOND
> +#   define QGA_MICRO_SECOND_TO_SECOND 1000000
> +#endif

I don't really understand the pattern here and elsewhere. Where else would
QGA_MICRO_SECOND_TO_SECOND ever be defined? I'd rather things break and
need to be reconciled explicitly if such a thing was ever introduced.

Everything else looks good though.

> +
> +static double ga_get_login_time(struct utmpx *user_info)
> +{
> +    double seconds = (double)user_info->ut_tv.tv_sec;
> +    double useconds = (double)user_info->ut_tv.tv_usec;
> +    useconds /= QGA_MICRO_SECOND_TO_SECOND;
> +    return seconds + useconds;
> +}
> +
> +GuestUserList *qmp_guest_get_users(Error **err)
> +{
> +    GHashTable *cache = g_hash_table_new(g_str_hash, g_str_equal);
> +    GuestUserList *head = NULL, *cur_item = NULL;
> +    setutxent();
> +    for (;;) {
> +        struct utmpx *user_info = getutxent();
> +        if (user_info == NULL) {
> +            break;
> +        } else if (user_info->ut_type != USER_PROCESS) {
> +            continue;
> +        } else if (g_hash_table_contains(cache, user_info->ut_user)) {
> +            gpointer value = g_hash_table_lookup(cache, user_info->ut_user);
> +            GuestUser *user = (GuestUser *)value;
> +            double login_time = ga_get_login_time(user_info);
> +            /* We're ensuring the earliest login time to be sent */
> +            if (login_time < user->login_time) {
> +                user->login_time = login_time;
> +            }
> +            continue;
> +        }
> +
> +        GuestUserList *item = g_new0(GuestUserList, 1);
> +        item->value = g_new0(GuestUser, 1);
> +        item->value->user = g_strdup(user_info->ut_user);
> +        item->value->login_time = ga_get_login_time(user_info);
> +
> +        g_hash_table_insert(cache, item->value->user, item->value);
> +
> +        if (!cur_item) {
> +            head = cur_item = item;
> +        } else {
> +            cur_item->next = item;
> +            cur_item = item;
> +        }
> +    }
> +    endutxent();
> +    g_hash_table_destroy(cache);
> +    return head;
> +}
> diff --git a/qga/commands-win32.c b/qga/commands-win32.c
> index 19d72b2..8b84a90 100644
> --- a/qga/commands-win32.c
> +++ b/qga/commands-win32.c
> @@ -11,6 +11,9 @@
>   * See the COPYING file in the top-level directory.
>   */
> 
> +#ifndef _WIN32_WINNT
> +#   define _WIN32_WINNT 0x0600
> +#endif
>  #include "qemu/osdep.h"
>  #include <wtypes.h>
>  #include <powrprof.h>
> @@ -25,6 +28,7 @@
>  #include <initguid.h>
>  #endif
>  #include <lm.h>
> +#include <wtsapi32.h>
> 
>  #include "qga/guest-agent-core.h"
>  #include "qga/vss-win32.h"
> @@ -1536,3 +1540,88 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
>          ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
>      }
>  }
> +
> +/* MINGW is missing two fields: IncomingFrames & OutgoingFrames */
> +typedef struct _GA_WTSINFOA {
> +    WTS_CONNECTSTATE_CLASS State;
> +    DWORD SessionId;
> +    DWORD IncomingBytes;
> +    DWORD OutgoingBytes;
> +    DWORD IncomingFrames;
> +    DWORD OutgoingFrames;
> +    DWORD IncomingCompressedBytes;
> +    DWORD OutgoingCompressedBy;
> +    CHAR WinStationName[WINSTATIONNAME_LENGTH];
> +    CHAR Domain[DOMAIN_LENGTH];
> +    CHAR UserName[USERNAME_LENGTH + 1];
> +    LARGE_INTEGER ConnectTime;
> +    LARGE_INTEGER DisconnectTime;
> +    LARGE_INTEGER LastInputTime;
> +    LARGE_INTEGER LogonTime;
> +    LARGE_INTEGER CurrentTime;
> +
> +} GA_WTSINFOA;
> +
> +GuestUserList *qmp_guest_get_users(Error **err)
> +{
> +#if (_WIN32_WINNT >= 0x0600)
> +#ifndef QGA_NANOSECONDS
> +#   define QGA_NANOSECONDS 10000000
> +#endif
> +#ifndef QGA_EPOCH_TIME_DIFF
> +#   define QGA_EPOCH_TIME_DIFF 116444736000000000LL
> +#endif
> +
> +    GHashTable *cache = g_hash_table_new(g_str_hash, g_str_equal);
> +    GuestUserList *head = NULL, *cur_item = NULL;
> +
> +    DWORD count = 0;
> +    WTS_SESSION_INFOA *entries = NULL;
> +    if (WTSEnumerateSessionsA(NULL, 0, 1, &entries, &count)) {
> +        for (DWORD i = 0; i < count; ++i) {
> +            DWORD buffer_size = 0;
> +            GA_WTSINFOA *session_info = NULL;
> +            if (WTSQuerySessionInformationA(
> +                NULL,
> +                entries[i].SessionId,
> +                WTSSessionInfo,
> +                (LPSTR *)&session_info,
> +                &buffer_size
> +            )) {
> +                if (strlen(session_info->UserName) == 0 ||
> +                    g_hash_table_contains(cache, session_info->UserName)) {
> +                    WTSFreeMemory(session_info);
> +                    continue;
> +                }
> +
> +                GuestUserList *item = g_new0(GuestUserList, 1);
> +                item->value = g_new0(GuestUser, 1);
> +
> +                item->value->user = g_strdup(session_info->UserName);
> +                item->value->domain = g_strdup(session_info->Domain);
> +                item->value->has_domain = true;
> +
> +                INT64 login = session_info->LogonTime.QuadPart;
> +                login -= QGA_EPOCH_TIME_DIFF;
> +                item->value->login_time = ((double)login) / QGA_NANOSECONDS;
> +
> +                g_hash_table_add(cache, item->value->user);
> +
> +                if (!cur_item) {
> +                    head = cur_item = item;
> +                } else {
> +                    cur_item->next = item;
> +                    cur_item = item;
> +                }
> +            }
> +            WTSFreeMemory(session_info);
> +        }
> +        WTSFreeMemory(entries);
> +    }
> +    g_hash_table_destroy(cache);
> +    return head;
> +#else
> +    error_setg(err, QERR_UNSUPPORTED);
> +    return NULL;
> +#endif
> +}
> diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> index a02dbf2..27d10be 100644
> --- a/qga/qapi-schema.json
> +++ b/qga/qapi-schema.json
> @@ -1042,3 +1042,27 @@
>    'data':    { 'path': 'str', '*arg': ['str'], '*env': ['str'],
>                 '*input-data': 'str', '*capture-output': 'bool' },
>    'returns': 'GuestExec' }
> +
> +##
> +# @GuestUser:
> +# @user:       Username
> +# @domain:     Logon domain (windows only)
> +# @login-time: Time of login of this user on the computer. If multiple
> +#              instances of the user are logged in, the earliest login time is
> +#              reported. The value is in fractional seconds since epoch time.
> +#
> +# Since: 2.10
> +##
> +{ 'struct': 'GuestUser',
> +  'data': { 'user': 'str', 'login-time': 'number', '*domain': 'str' } }
> +
> +##
> +# @guest-get-users:
> +# Retrieves a list of currently active users on the VM.
> +#
> +# Returns: A unique list of users.
> +#
> +# Since: 2.10
> +##
> +{ 'command': 'guest-get-users',
> +  'returns': ['GuestUser'] }
> -- 
> 2.9.3
> 
> 

      parent reply	other threads:[~2017-04-12 20:08 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-04-04  5:51 [Qemu-devel] [PATCH v3 0/1] qga: Add 'guest-get-users' command Vinzenz 'evilissimo' Feenstra
2017-04-04  5:51 ` [Qemu-devel] [PATCH v3 1/1] " Vinzenz 'evilissimo' Feenstra
2017-04-11  9:01   ` Vinzenz Feenstra
2017-04-12 20:07   ` Michael Roth [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=149202766263.22939.9361142502968019871@loki \
    --to=mdroth@linux.vnet.ibm.com \
    --cc=marcandre.lureau@gmail.com \
    --cc=qemu-devel@nongnu.org \
    --cc=vfeenstr@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.