VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp@ 102753

Last change on this file since 102753 was 102753, checked in by vboxsync, 5 months ago

Guest Properties/VBoxService: Deal with long(er) user name / domain name combinations on Windows guests. See comments for details. bugref:10575

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 80.3 KB
Line 
1/* $Id: VBoxServiceVMInfo.cpp 102753 2024-01-03 17:24:32Z vboxsync $ */
2/** @file
3 * VBoxService - Virtual Machine Information for the Host.
4 */
5
6/*
7 * Copyright (C) 2009-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/** @page pg_vgsvc_vminfo VBoxService - VM Information
29 *
30 * The VM Information subservice provides heaps of useful information about the
31 * VM via guest properties.
32 *
33 * Guest properties is a limited database maintained by the HGCM GuestProperties
34 * service in cooperation with the Main API (VBoxSVC). Properties have a name
35 * (ours are path like), a string value, and a nanosecond timestamp (unix
36 * epoch). The timestamp lets the user see how recent the information is. As
37 * an laternative to polling on changes, it is also possible to wait on changes
38 * via the Main API or VBoxManage on the host side and VBoxControl in the guest.
39 *
40 * The namespace "/VirtualBox/" is reserved for value provided by VirtualBox.
41 * This service provides all the information under "/VirtualBox/GuestInfo/".
42 *
43 *
44 * @section sec_vgsvc_vminfo_beacons Beacons
45 *
46 * The subservice does not write properties unless there are changes. So, in
47 * order for the host side to know that information is up to date despite an
48 * oldish timestamp we define a couple of values that are always updated and can
49 * reliably used to figure how old the information actually is.
50 *
51 * For the networking part "/VirtualBox/GuestInfo/Net/Count" is the value to
52 * watch out for.
53 *
54 * For the login part, it's possible that we intended to use
55 * "/VirtualBox/GuestInfo/OS/LoggedInUsers" for this, however it is not defined
56 * correctly and current does NOT work as a beacon.
57 *
58 */
59
60
61/*********************************************************************************************************************************
62* Header Files *
63*********************************************************************************************************************************/
64#ifdef RT_OS_WINDOWS
65# include <iprt/win/winsock2.h>
66# include <iprt/win/iphlpapi.h>
67# include <iprt/win/ws2tcpip.h>
68# include <iprt/win/windows.h>
69# include <Ntsecapi.h>
70#else
71# define __STDC_LIMIT_MACROS
72# include <arpa/inet.h>
73# include <errno.h>
74# include <netinet/in.h>
75# include <sys/ioctl.h>
76# include <sys/socket.h>
77# include <net/if.h>
78# include <pwd.h> /* getpwuid */
79# include <unistd.h>
80# if !defined(RT_OS_OS2) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_HAIKU)
81# include <utmpx.h> /** @todo FreeBSD 9 should have this. */
82# endif
83# ifdef RT_OS_OS2
84# include <net/if_dl.h>
85# endif
86# ifdef RT_OS_SOLARIS
87# include <sys/sockio.h>
88# include <net/if_arp.h>
89# endif
90# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
91# include <ifaddrs.h> /* getifaddrs, freeifaddrs */
92# include <net/if_dl.h> /* LLADDR */
93# include <netdb.h> /* getnameinfo */
94# endif
95# ifdef VBOX_WITH_DBUS
96# include <VBox/dbus.h>
97# endif
98#endif
99
100#include <iprt/mem.h>
101#include <iprt/thread.h>
102#include <iprt/string.h>
103#include <iprt/semaphore.h>
104#include <iprt/system.h>
105#include <iprt/time.h>
106#include <iprt/assert.h>
107#include <VBox/err.h>
108#include <VBox/version.h>
109#include <VBox/VBoxGuestLib.h>
110#include <VBox/HostServices/GuestPropertySvc.h> /* For GUEST_PROP_MAX_VALUE_LEN */
111#include "VBoxServiceInternal.h"
112#include "VBoxServiceUtils.h"
113#include "VBoxServicePropCache.h"
114
115
116/** Structure containing information about a location awarness
117 * client provided by the host. */
118/** @todo Move this (and functions) into VbglR3. */
119typedef struct VBOXSERVICELACLIENTINFO
120{
121 uint32_t uID;
122 char *pszName;
123 char *pszLocation;
124 char *pszDomain;
125 bool fAttached;
126 uint64_t uAttachedTS;
127} VBOXSERVICELACLIENTINFO, *PVBOXSERVICELACLIENTINFO;
128
129
130/*********************************************************************************************************************************
131* Global Variables *
132*********************************************************************************************************************************/
133/** The vminfo interval (milliseconds). */
134static uint32_t g_cMsVMInfoInterval = 0;
135/** The semaphore we're blocking on. */
136static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
137/** The guest property service client ID. */
138static uint32_t g_uVMInfoGuestPropSvcClientID = 0;
139/** Number of currently logged in users in OS. */
140static uint32_t g_cVMInfoLoggedInUsers = 0;
141/** The guest property cache. */
142static VBOXSERVICEVEPROPCACHE g_VMInfoPropCache;
143static const char *g_pszPropCacheValLoggedInUsersList = "/VirtualBox/GuestInfo/OS/LoggedInUsersList";
144static const char *g_pszPropCacheValLoggedInUsers = "/VirtualBox/GuestInfo/OS/LoggedInUsers";
145static const char *g_pszPropCacheValNoLoggedInUsers = "/VirtualBox/GuestInfo/OS/NoLoggedInUsers";
146static const char *g_pszPropCacheValNetCount = "/VirtualBox/GuestInfo/Net/Count";
147/** A guest user's guest property root key. */
148static const char *g_pszPropCacheKeyUser = "/VirtualBox/GuestInfo/User";
149/** The VM session ID. Changes whenever the VM is restored or reset. */
150static uint64_t g_idVMInfoSession;
151/** The last attached locartion awareness (LA) client timestamp. */
152static uint64_t g_LAClientAttachedTS = 0;
153/** The current LA client info. */
154static VBOXSERVICELACLIENTINFO g_LAClientInfo;
155/** User idle threshold (in ms). This specifies the minimum time a user is considered
156 * as being idle and then will be reported to the host. Default is 5s. */
157uint32_t g_uVMInfoUserIdleThresholdMS = 5 * 1000;
158
159
160/*********************************************************************************************************************************
161* Defines *
162*********************************************************************************************************************************/
163static const char *g_pszLAActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient";
164
165#ifdef VBOX_WITH_DBUS
166/** @name ConsoleKit defines (taken from 0.4.5).
167 * @{ */
168# define CK_NAME "org.freedesktop.ConsoleKit" /* unused */
169# define CK_PATH "/org/freedesktop/ConsoleKit" /* unused */
170# define CK_INTERFACE "org.freedesktop.ConsoleKit"
171# define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager"
172# define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
173# define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" /* unused */
174# define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session"
175/** @} */
176/** @name systemd-logind defines
177 * @{ */
178# define SYSTEMD_LOGIN_INTERFACE "org.freedesktop.login1"
179# define SYSTEMD_LOGIN_PATH "/org/freedesktop/login1"
180# define SYSTEMD_LOGIN_MANAGER_INTERFACE "org.freedesktop.login1.Manager"
181# define SYSTEMD_LOGIN_SESSION_INTERFACE "org.freedesktop.login1.Session"
182/** @} */
183#endif
184
185
186
187/**
188 * Signals the event so that a re-enumeration of VM-specific
189 * information (like logged in users) can happen.
190 *
191 * @return IPRT status code.
192 */
193int VGSvcVMInfoSignal(void)
194{
195 /* Trigger a re-enumeration of all logged-in users by unblocking
196 * the multi event semaphore of the VMInfo thread. */
197 if (g_hVMInfoEvent)
198 return RTSemEventMultiSignal(g_hVMInfoEvent);
199
200 return VINF_SUCCESS;
201}
202
203
204/**
205 * @interface_method_impl{VBOXSERVICE,pfnPreInit}
206 */
207static DECLCALLBACK(int) vbsvcVMInfoPreInit(void)
208{
209 return VINF_SUCCESS;
210}
211
212
213/**
214 * @interface_method_impl{VBOXSERVICE,pfnOption}
215 */
216static DECLCALLBACK(int) vbsvcVMInfoOption(const char **ppszShort, int argc, char **argv, int *pi)
217{
218 /** @todo Use RTGetOpt here. */
219
220 int rc = -1;
221 if (ppszShort)
222 /* no short options */;
223 else if (!strcmp(argv[*pi], "--vminfo-interval"))
224 rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1);
225 else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold"))
226 rc = VGSvcArgUInt32(argc, argv, "", pi, &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1);
227 return rc;
228}
229
230
231/**
232 * @interface_method_impl{VBOXSERVICE,pfnInit}
233 */
234static DECLCALLBACK(int) vbsvcVMInfoInit(void)
235{
236 /*
237 * If not specified, find the right interval default.
238 * Then create the event sem to block on.
239 */
240 if (!g_cMsVMInfoInterval)
241 g_cMsVMInfoInterval = g_DefaultInterval * 1000;
242 if (!g_cMsVMInfoInterval)
243 {
244 /* Set it to 5s by default for location awareness checks. */
245 g_cMsVMInfoInterval = 5 * 1000;
246 }
247
248 int rc = RTSemEventMultiCreate(&g_hVMInfoEvent);
249 AssertRCReturn(rc, rc);
250
251 VbglR3GetSessionId(&g_idVMInfoSession);
252 /* The status code is ignored as this information is not available with VBox < 3.2.10. */
253
254 /* Initialize the LA client object. */
255 RT_ZERO(g_LAClientInfo);
256
257 rc = VbglR3GuestPropConnect(&g_uVMInfoGuestPropSvcClientID);
258 if (RT_SUCCESS(rc))
259 VGSvcVerbose(3, "Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID);
260 else
261 {
262 /* If the service was not found, we disable this service without
263 causing VBoxService to fail. */
264 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
265 {
266 VGSvcVerbose(0, "Guest property service is not available, disabling the service\n");
267 rc = VERR_SERVICE_DISABLED;
268 }
269 else
270 VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
271 RTSemEventMultiDestroy(g_hVMInfoEvent);
272 g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
273 }
274
275 if (RT_SUCCESS(rc))
276 {
277 VGSvcPropCacheCreate(&g_VMInfoPropCache, g_uVMInfoGuestPropSvcClientID);
278
279 /*
280 * Declare some guest properties with flags and reset values.
281 */
282 int rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList,
283 VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
284 NULL /* Delete on exit */);
285 if (RT_FAILURE(rc2))
286 VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2);
287
288 rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers,
289 VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "0");
290 if (RT_FAILURE(rc2))
291 VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2);
292
293 rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers,
294 VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "true");
295 if (RT_FAILURE(rc2))
296 VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2);
297
298 rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount,
299 VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE,
300 NULL /* Delete on exit */);
301 if (RT_FAILURE(rc2))
302 VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNetCount, rc2);
303
304 /*
305 * Get configuration guest properties from the host.
306 * Note: All properties should have sensible defaults in case the lookup here fails.
307 */
308 char *pszValue;
309 rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold",
310 true /* Read only */, &pszValue, NULL /* Flags */, NULL /* Timestamp */);
311 if (RT_SUCCESS(rc2))
312 {
313 AssertPtr(pszValue);
314 g_uVMInfoUserIdleThresholdMS = RT_CLAMP(RTStrToUInt32(pszValue), 1000, UINT32_MAX - 1);
315 RTStrFree(pszValue);
316 }
317 }
318 return rc;
319}
320
321
322/**
323 * Retrieves a specifiy client LA property.
324 *
325 * @return IPRT status code.
326 * @param uClientID LA client ID to retrieve property for.
327 * @param pszProperty Property (without path) to retrieve.
328 * @param ppszValue Where to store value of property.
329 * @param puTimestamp Timestamp of property to retrieve. Optional.
330 */
331static int vgsvcGetLAClientValue(uint32_t uClientID, const char *pszProperty, char **ppszValue, uint64_t *puTimestamp)
332{
333 AssertReturn(uClientID, VERR_INVALID_PARAMETER);
334 AssertPtrReturn(pszProperty, VERR_INVALID_POINTER);
335
336 int rc;
337
338 char pszClientPath[255];
339/** @todo r=bird: Another pointless RTStrPrintf test with wrong status code to boot. */
340 if (RTStrPrintf(pszClientPath, sizeof(pszClientPath),
341 "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty))
342 {
343 rc = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, pszClientPath, true /* Read only */,
344 ppszValue, NULL /* Flags */, puTimestamp);
345 }
346 else
347 rc = VERR_NO_MEMORY;
348
349 return rc;
350}
351
352
353/**
354 * Retrieves LA client information. On success the returned structure will have allocated
355 * objects which need to be free'd with vboxServiceFreeLAClientInfo.
356 *
357 * @return IPRT status code.
358 * @param uClientID Client ID to retrieve information for.
359 * @param pClient Pointer where to store the client information.
360 */
361static int vgsvcGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient)
362{
363 AssertReturn(uClientID, VERR_INVALID_PARAMETER);
364 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
365
366 int rc = vgsvcGetLAClientValue(uClientID, "Name", &pClient->pszName,
367 NULL /* Timestamp */);
368 if (RT_SUCCESS(rc))
369 {
370 char *pszAttach;
371 rc = vgsvcGetLAClientValue(uClientID, "Attach", &pszAttach, &pClient->uAttachedTS);
372 if (RT_SUCCESS(rc))
373 {
374 AssertPtr(pszAttach);
375 pClient->fAttached = RTStrICmp(pszAttach, "1") == 0;
376
377 RTStrFree(pszAttach);
378 }
379 }
380 if (RT_SUCCESS(rc))
381 rc = vgsvcGetLAClientValue(uClientID, "Location", &pClient->pszLocation, NULL /* Timestamp */);
382 if (RT_SUCCESS(rc))
383 rc = vgsvcGetLAClientValue(uClientID, "Domain", &pClient->pszDomain, NULL /* Timestamp */);
384 if (RT_SUCCESS(rc))
385 pClient->uID = uClientID;
386
387 return rc;
388}
389
390
391/**
392 * Frees all allocated LA client information of a structure.
393 *
394 * @param pClient Pointer to client information structure to free.
395 */
396static void vgsvcFreeLAClientInfo(PVBOXSERVICELACLIENTINFO pClient)
397{
398 if (pClient)
399 {
400 if (pClient->pszName)
401 {
402 RTStrFree(pClient->pszName);
403 pClient->pszName = NULL;
404 }
405 if (pClient->pszLocation)
406 {
407 RTStrFree(pClient->pszLocation);
408 pClient->pszLocation = NULL;
409 }
410 if (pClient->pszDomain)
411 {
412 RTStrFree(pClient->pszDomain);
413 pClient->pszDomain = NULL;
414 }
415 }
416}
417
418
419/**
420 * Updates a per-guest user guest property inside the given property cache.
421 *
422 * @return VBox status code.
423 * @retval VERR_BUFFER_OVERFLOW if the final property name length exceeds the maximum supported length.
424 * @param pCache Pointer to guest property cache to update user in.
425 * @param pszUser Name of guest user to update.
426 * @param pszDomain Domain of guest user to update. Optional.
427 * @param pszKey Key name of guest property to update.
428 * @param pszValueFormat Guest property value to set. Pass NULL for deleting
429 * the property.
430 */
431int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
432 const char *pszKey, const char *pszValueFormat, ...)
433{
434 AssertPtrReturn(pCache, VERR_INVALID_POINTER);
435 AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
436 /* pszDomain is optional. */
437 AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
438 /* pszValueFormat is optional. */
439
440 /** Historically we limit guest property names to 64 characters (see GUEST_PROP_MAX_NAME_LEN, including terminator).
441 * So we need to make sure the stuff we want to write as a value fits into that space. See bugref{10575}. */
442
443 /* Try to write things the legacy way first. */
444 char szName[GUEST_PROP_MAX_NAME_LEN];
445 AssertCompile(GUEST_PROP_MAX_NAME_LEN == 64); /* Can we improve stuff once we (ever) raise this limit? */
446 ssize_t const cchVal = pszDomain
447 ? RTStrPrintf2(szName, sizeof(szName), "%s/%s@%s/%s", g_pszPropCacheKeyUser, pszUser, pszDomain, pszKey)
448 : RTStrPrintf2(szName, sizeof(szName), "%s/%s/%s", g_pszPropCacheKeyUser, pszUser, pszKey);
449
450 /* Did we exceed the length limit? Tell the caller to try again with some more sane values. */
451 if (cchVal < 0)
452 return VERR_BUFFER_OVERFLOW;
453
454 int rc = VINF_SUCCESS;
455
456 char *pszValue = NULL;
457 if (pszValueFormat)
458 {
459 va_list va;
460 va_start(va, pszValueFormat);
461 if (RTStrAPrintfV(&pszValue, pszValueFormat, va) < 0)
462 rc = VERR_NO_MEMORY;
463 va_end(va);
464 if ( RT_SUCCESS(rc)
465 && !pszValue)
466 rc = VERR_NO_STR_MEMORY;
467 }
468
469 if (RT_SUCCESS(rc))
470 rc = VGSvcPropCacheUpdate(pCache, szName, pszValue);
471 if (rc == VINF_SUCCESS) /* VGSvcPropCacheUpdate will also return VINF_NO_CHANGE. */
472 {
473 /** @todo Combine updating flags w/ updating the actual value. */
474 rc = VGSvcPropCacheUpdateEntry(pCache, szName,
475 VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
476 NULL /* Delete on exit */);
477 }
478
479 RTStrFree(pszValue);
480 return rc;
481}
482
483
484/**
485 * Updates a per-guest user guest property inside the given property cache.
486 *
487 * @return VBox status code.
488 * @retval VERR_BUFFER_OVERFLOW if the final property name length exceeds the maximum supported length.
489 * @param pCache Pointer to guest property cache to update user in.
490 * @param pszUser Name of guest user to update.
491 * @param pszDomain Domain of guest user to update. Optional.
492 * @param pszKey Key name of guest property to update.
493 * @param pszFormat Format string to set. Pass NULL for deleting the property.
494 * @param va Format arguments.
495 */
496int VGSvcUserUpdateV(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
497 const char *pszKey, const char *pszFormat, va_list va)
498{
499 char *psz = NULL;
500 if (pszFormat) /* Might be NULL to delete a property. */
501 {
502 if (RTStrAPrintfV(&psz, pszFormat, va) < 0)
503 return VERR_NO_MEMORY;
504 }
505 int const rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, pszKey, psz);
506 RTStrFree(psz);
507 return rc;
508}
509
510
511/**
512 * Writes the properties that won't change while the service is running.
513 *
514 * Errors are ignored.
515 */
516static void vgsvcVMInfoWriteFixedProperties(void)
517{
518 /*
519 * First get OS information that won't change.
520 */
521 char szInfo[256];
522 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szInfo, sizeof(szInfo));
523 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Product",
524 "%s", RT_FAILURE(rc) ? "" : szInfo);
525
526 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo));
527 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Release",
528 "%s", RT_FAILURE(rc) ? "" : szInfo);
529
530 rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szInfo, sizeof(szInfo));
531 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Version",
532 "%s", RT_FAILURE(rc) ? "" : szInfo);
533
534 rc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szInfo, sizeof(szInfo));
535 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/ServicePack",
536 "%s", RT_FAILURE(rc) ? "" : szInfo);
537
538 /*
539 * Retrieve version information about Guest Additions and installed files (components).
540 */
541 char *pszAddVer;
542 char *pszAddVerExt;
543 char *pszAddRev;
544 rc = VbglR3GetAdditionsVersion(&pszAddVer, &pszAddVerExt, &pszAddRev);
545 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Version",
546 "%s", RT_FAILURE(rc) ? "" : pszAddVer);
547 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VersionExt",
548 "%s", RT_FAILURE(rc) ? "" : pszAddVerExt);
549 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Revision",
550 "%s", RT_FAILURE(rc) ? "" : pszAddRev);
551 if (RT_SUCCESS(rc))
552 {
553 RTStrFree(pszAddVer);
554 RTStrFree(pszAddVerExt);
555 RTStrFree(pszAddRev);
556 }
557
558#ifdef RT_OS_WINDOWS
559 /*
560 * Do windows specific properties.
561 */
562 char *pszInstDir;
563 rc = VbglR3GetAdditionsInstallationPath(&pszInstDir);
564 VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/InstallDir",
565 "%s", RT_FAILURE(rc) ? "" : pszInstDir);
566 if (RT_SUCCESS(rc))
567 RTStrFree(pszInstDir);
568
569 VGSvcVMInfoWinGetComponentVersions(g_uVMInfoGuestPropSvcClientID);
570#endif
571}
572
573
574#if defined(VBOX_WITH_DBUS) && defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
575/*
576 * Simple wrappers to work around compiler-specific va_list madness.
577 */
578static dbus_bool_t vboxService_dbus_message_get_args(DBusMessage *message, DBusError *error, int first_arg_type, ...)
579{
580 va_list va;
581 va_start(va, first_arg_type);
582 dbus_bool_t ret = dbus_message_get_args_valist(message, error, first_arg_type, va);
583 va_end(va);
584 return ret;
585}
586
587static dbus_bool_t vboxService_dbus_message_append_args(DBusMessage *message, int first_arg_type, ...)
588{
589 va_list va;
590 va_start(va, first_arg_type);
591 dbus_bool_t ret = dbus_message_append_args_valist(message, first_arg_type, va);
592 va_end(va);
593 return ret;
594}
595
596#ifndef DBUS_TYPE_VARIANT
597#define DBUS_TYPE_VARIANT ((int) 'v')
598#endif
599/*
600 * Wrapper to dig values out of dbus replies, which are contained in
601 * a 'variant' and must be iterated into twice.
602 *
603 * Returns true if it thinks it got a value; false if not.
604 *
605 * This does various error checking so the caller can skip it:
606 * - whether a DBusError is set
607 * - whether the DBusMessage is valid
608 * - whether we actually got a 'variant'
609 * - whether we got the type the caller's looking for
610 */
611static bool vboxService_dbus_unpack_variant_reply(DBusError *error, DBusMessage *pReply, char pType, void *pValue)
612{
613 if (dbus_error_is_set(error))
614 {
615 VGSvcError("dbus_unpack_variant_reply: dbus returned error '%s'\n", error->message);
616 dbus_error_free(error);
617 }
618 else if (pReply)
619 {
620 DBusMessageIter iterMsg;
621 int iterType;
622 dbus_message_iter_init(pReply, &iterMsg);
623 iterType = dbus_message_iter_get_arg_type(&iterMsg);
624 if (iterType == DBUS_TYPE_VARIANT)
625 {
626 DBusMessageIter iterValueMsg;
627 int iterValueType;
628 dbus_message_iter_recurse(&iterMsg, &iterValueMsg);
629 iterValueType = dbus_message_iter_get_arg_type(&iterValueMsg);
630 if (iterValueType == pType)
631 {
632 dbus_message_iter_get_basic(&iterValueMsg, pValue);
633 return true;
634 }
635 }
636 }
637 return false;
638}
639
640/*
641 * Wrapper to NULL out the DBusMessage pointer while discarding it.
642 * DBus API is multi-threaded and can have multiple concurrent accessors.
643 * Our use here is single-threaded and can never have multiple accessors.
644 */
645static void vboxService_dbus_message_discard(DBusMessage **ppMsg)
646{
647 if (ppMsg && *ppMsg)
648 {
649 /** @todo any clean-ish way to verify DBus internal refcount == 1 here? */
650 dbus_message_unref(*ppMsg);
651 *ppMsg = NULL;
652 }
653}
654#endif
655
656
657/*
658 * Add a user to the list of active users (while ignoring duplicates
659 * and dynamically maintaining the list storage)
660 */
661#define USER_LIST_CHUNK_SIZE 32
662static uint32_t cUsersInList;
663static uint32_t cListSize;
664static char **papszUsers;
665
666static void vgsvcVMInfoAddUserToList(const char *name, const char *src)
667{
668 int rc;
669 bool fFound = false;
670 for (uint32_t idx = 0; idx < cUsersInList && !fFound; idx++)
671 fFound = strncmp(papszUsers[idx], name, 32) == 0;
672 VGSvcVerbose(5, "LoggedInUsers: Asked to add user '%s' from '%s' to list (already in list = %lu)\n", name, src, fFound);
673 if (!fFound)
674 {
675 if (cUsersInList + 1 > cListSize)
676 {
677 VGSvcVerbose(5, "LoggedInUsers: increase user list size from %lu to %lu\n", cListSize, cListSize + USER_LIST_CHUNK_SIZE);
678 cListSize += USER_LIST_CHUNK_SIZE;
679 void *pvNew = RTMemRealloc(papszUsers, cListSize * sizeof(char*));
680 AssertReturnVoidStmt(pvNew, cListSize -= USER_LIST_CHUNK_SIZE);
681 papszUsers = (char **)pvNew;
682 }
683 VGSvcVerbose(4, "LoggedInUsers: Adding user '%s' from '%s' to list (size = %lu, count = %lu)\n", name, src, cListSize, cUsersInList);
684 rc = RTStrDupEx(&papszUsers[cUsersInList], name);
685 if (!RT_FAILURE(rc))
686 cUsersInList++;
687 }
688}
689
690/**
691 * Provide information about active users.
692 */
693static int vgsvcVMInfoWriteUsers(void)
694{
695 int rc;
696 char *pszUserList = NULL;
697
698 cUsersInList = 0;
699
700#ifdef RT_OS_WINDOWS
701 rc = VGSvcVMInfoWinWriteUsers(&g_VMInfoPropCache, &pszUserList, &cUsersInList);
702
703#elif defined(RT_OS_FREEBSD)
704 /** @todo FreeBSD: Port logged on user info retrieval.
705 * However, FreeBSD 9 supports utmpx, so we could use the code
706 * block below (?). */
707 rc = VERR_NOT_IMPLEMENTED;
708
709#elif defined(RT_OS_HAIKU)
710 /** @todo Haiku: Port logged on user info retrieval. */
711 rc = VERR_NOT_IMPLEMENTED;
712
713#elif defined(RT_OS_OS2)
714 /** @todo OS/2: Port logged on (LAN/local/whatever) user info retrieval. */
715 rc = VERR_NOT_IMPLEMENTED;
716
717#else
718 setutxent();
719 utmpx *ut_user;
720 cListSize = USER_LIST_CHUNK_SIZE;
721
722 /* Allocate a first array to hold 32 users max. */
723 papszUsers = (char **)RTMemAllocZ(cListSize * sizeof(char *));
724 if (papszUsers)
725 rc = VINF_SUCCESS;
726 else
727 rc = VERR_NO_MEMORY;
728
729 /* Process all entries in the utmp file.
730 * Note: This only handles */
731 while ( (ut_user = getutxent())
732 && RT_SUCCESS(rc))
733 {
734# ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */
735 VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32)\n", ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid);
736# else
737 VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32, session: %RU32)\n",
738 ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid, ut_user->ut_session);
739# endif
740
741 /* Make sure we don't add user names which are not
742 * part of type USER_PROCES. */
743 if (ut_user->ut_type == USER_PROCESS) /* Regular user process. */
744 {
745 vgsvcVMInfoAddUserToList(ut_user->ut_user, "utmpx");
746 }
747 }
748
749# ifdef VBOX_WITH_DBUS
750# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
751 DBusError dbErr;
752 DBusConnection *pConnection = NULL;
753 int rc2 = RTDBusLoadLib();
754 bool fHaveLibDbus = false;
755 if (RT_SUCCESS(rc2))
756 {
757 /* Handle desktop sessions using systemd-logind. */
758 VGSvcVerbose(4, "Checking systemd-logind sessions ...\n");
759 fHaveLibDbus = true;
760 dbus_error_init(&dbErr);
761 pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr);
762 }
763
764 if ( pConnection
765 && !dbus_error_is_set(&dbErr))
766 {
767/** @todo is there some Less Horrible Way(tm) to access dbus? */
768 /* Get all available sessions. */
769 /* like `busctl call org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager ListSessions` */
770 DBusMessage *pMsgSessions = dbus_message_new_method_call(SYSTEMD_LOGIN_INTERFACE,
771 SYSTEMD_LOGIN_PATH,
772 SYSTEMD_LOGIN_MANAGER_INTERFACE,
773 "ListSessions");
774 if ( pMsgSessions
775 && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL)
776 {
777 DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection,
778 pMsgSessions, 30 * 1000 /* 30s timeout */,
779 &dbErr);
780 if ( pReplySessions
781 && !dbus_error_is_set(&dbErr))
782 {
783 /* dbus_message_new_method_call() returns a DBusMessage, which we must iterate to get the returned value */
784 DBusMessageIter messageIterMsg;
785 int messageIterType;
786 dbus_message_iter_init(pReplySessions, &messageIterMsg);
787 while ((messageIterType = dbus_message_iter_get_arg_type (&messageIterMsg)) != DBUS_TYPE_INVALID)
788 {
789 if (messageIterType == DBUS_TYPE_ARRAY)
790 {
791 /* "ListSessions" returns an array, which we must iterate to get the array elements */
792 DBusMessageIter arrayIterMsg;
793 int arrayIterType;
794 dbus_message_iter_recurse(&messageIterMsg, &arrayIterMsg);
795 while ((arrayIterType = dbus_message_iter_get_arg_type (&arrayIterMsg)) != DBUS_TYPE_INVALID)
796 {
797 if (arrayIterType == DBUS_TYPE_STRUCT)
798 {
799 /* The array elements are structs, which we must iterate to get the struct elements */
800 DBusMessageIter structIterMsg;
801 int structIterType;
802 dbus_message_iter_recurse(&arrayIterMsg, &structIterMsg);
803 while ((structIterType = dbus_message_iter_get_arg_type (&structIterMsg)) != DBUS_TYPE_INVALID)
804 {
805 if (structIterType == DBUS_TYPE_OBJECT_PATH)
806 {
807 /* We are interested only in the "object path" struct element */
808 const char *objectPath;
809 dbus_message_iter_get_basic(&structIterMsg, &objectPath);
810 const char *pInterface = SYSTEMD_LOGIN_SESSION_INTERFACE;
811 /* Create and send a new dbus query asking for that session's details */
812 DBusMessage *pMsgSession = dbus_message_new_method_call(SYSTEMD_LOGIN_INTERFACE,
813 objectPath,
814 "org.freedesktop.DBus.Properties",
815 "Get");
816 if ( pMsgSession
817 && dbus_message_get_type(pMsgSession) == DBUS_MESSAGE_TYPE_METHOD_CALL)
818 {
819 const char *pPropertyActive = "Active";
820 vboxService_dbus_message_append_args(pMsgSession,
821 DBUS_TYPE_STRING, &pInterface,
822 DBUS_TYPE_STRING, &pPropertyActive,
823 DBUS_TYPE_INVALID, 0);
824 /* like `busctl get-property org.freedesktop.login1 %s org.freedesktop.login1.Session Active` %(objectPath) */
825 DBusMessage *pReplySession = dbus_connection_send_with_reply_and_block(
826 pConnection,
827 pMsgSession,
828 -1,
829 &dbErr);
830 int sessionPropertyActiveValue;
831 if ( vboxService_dbus_unpack_variant_reply(
832 &dbErr,
833 pReplySession,
834 DBUS_TYPE_BOOLEAN,
835 &sessionPropertyActiveValue)
836 && sessionPropertyActiveValue)
837 {
838 DBusMessage *pMsgSession2 = dbus_message_new_method_call(SYSTEMD_LOGIN_INTERFACE,
839 objectPath,
840 "org.freedesktop.DBus.Properties",
841 "Get");
842 const char *pPropertyName = "Name";
843 if ( pMsgSession2
844 && dbus_message_get_type(pMsgSession2) == DBUS_MESSAGE_TYPE_METHOD_CALL)
845 {
846 vboxService_dbus_message_append_args(pMsgSession2,
847 DBUS_TYPE_STRING, &pInterface,
848 DBUS_TYPE_STRING, &pPropertyName,
849 DBUS_TYPE_INVALID, 0);
850 /* like `busctl get-property org.freedesktop.login1 %s org.freedesktop.login1.Session Name` %(objectPath) */
851 DBusMessage *pReplyName = dbus_connection_send_with_reply_and_block(
852 pConnection,
853 pMsgSession2,
854 -1,
855 &dbErr);
856 const char *sessionPropertyNameValue;
857 if ( vboxService_dbus_unpack_variant_reply(
858 &dbErr,
859 pReplyName,
860 DBUS_TYPE_STRING,
861 &sessionPropertyNameValue)
862 && sessionPropertyNameValue)
863 vgsvcVMInfoAddUserToList(sessionPropertyNameValue, "systemd-logind");
864 vboxService_dbus_message_discard(&pReplyName);
865 }
866 vboxService_dbus_message_discard(&pMsgSession2);
867 }
868 vboxService_dbus_message_discard(&pReplySession);
869 }
870 vboxService_dbus_message_discard(&pMsgSession);
871 }
872 dbus_message_iter_next (&structIterMsg);
873 }
874 }
875 dbus_message_iter_next (&arrayIterMsg);
876 }
877 }
878 dbus_message_iter_next (&messageIterMsg);
879 }
880 vboxService_dbus_message_discard(&pReplySessions);
881 }
882 }
883 else
884 {
885 static int s_iBitchedAboutSystemdLogind = 0;
886 if (s_iBitchedAboutSystemdLogind < 3)
887 {
888 s_iBitchedAboutSystemdLogind++;
889 VGSvcError("Unable to invoke systemd-logind (%d/3) -- maybe not installed / used? Error: %s\n",
890 s_iBitchedAboutSystemdLogind,
891 dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
892 }
893 }
894
895 vboxService_dbus_message_discard(&pMsgSessions);
896 if (dbus_error_is_set(&dbErr))
897 {
898 dbus_error_free(&dbErr);
899 }
900 }
901 if (RT_SUCCESS(rc2))
902 {
903 /* Handle desktop sessions using ConsoleKit. */
904 VGSvcVerbose(4, "Checking ConsoleKit sessions ...\n");
905 fHaveLibDbus = true;
906 dbus_error_init(&dbErr);
907 /** @todo should this be dbus_connection_open() (and below, dbus_connection_unref())? */
908 pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr);
909 }
910
911 if ( pConnection
912 && !dbus_error_is_set(&dbErr))
913 {
914 /* Get all available sessions. */
915 DBusMessage *pMsgSessions = dbus_message_new_method_call(CK_INTERFACE,
916 CK_MANAGER_PATH,
917 CK_MANAGER_INTERFACE,
918 "GetSessions");
919 if ( pMsgSessions
920 && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL)
921 {
922 DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection,
923 pMsgSessions, 30 * 1000 /* 30s timeout */,
924 &dbErr);
925 if ( pReplySessions
926 && !dbus_error_is_set(&dbErr))
927 {
928 char **ppszSessions;
929 int cSessions;
930 if ( dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL
931 && vboxService_dbus_message_get_args(pReplySessions, &dbErr, DBUS_TYPE_ARRAY,
932 DBUS_TYPE_OBJECT_PATH, &ppszSessions, &cSessions,
933 DBUS_TYPE_INVALID /* Termination */))
934 {
935 VGSvcVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions);
936
937 char **ppszCurSession = ppszSessions;
938 for (ppszCurSession; ppszCurSession && *ppszCurSession; ppszCurSession++)
939 {
940 VGSvcVerbose(4, "ConsoleKit: processing session '%s' ...\n", *ppszCurSession);
941
942 /* Only respect active sessions .*/
943 bool fActive = false;
944 DBusMessage *pMsgSessionActive = dbus_message_new_method_call(CK_INTERFACE,
945 *ppszCurSession,
946 CK_SESSION_INTERFACE,
947 "IsActive");
948 if ( pMsgSessionActive
949 && dbus_message_get_type(pMsgSessionActive) == DBUS_MESSAGE_TYPE_METHOD_CALL)
950 {
951 DBusMessage *pReplySessionActive = dbus_connection_send_with_reply_and_block(pConnection,
952 pMsgSessionActive,
953 30 * 1000 /*sec*/,
954 &dbErr);
955 if ( pReplySessionActive
956 && !dbus_error_is_set(&dbErr))
957 {
958 DBusMessageIter itMsg;
959 if ( dbus_message_iter_init(pReplySessionActive, &itMsg)
960 && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_BOOLEAN)
961 {
962 /* Get uid from message. */
963 int val;
964 dbus_message_iter_get_basic(&itMsg, &val);
965 fActive = val >= 1;
966 }
967
968 }
969 /** @todo clean up if &dbErr */
970 vboxService_dbus_message_discard(&pReplySessionActive);
971
972 vboxService_dbus_message_discard(&pMsgSessionActive);
973 }
974
975 VGSvcVerbose(4, "ConsoleKit: session '%s' is %s\n",
976 *ppszCurSession, fActive ? "active" : "not active");
977
978 /* *ppszCurSession now contains the object path
979 * (e.g. "/org/freedesktop/ConsoleKit/Session1"). */
980 DBusMessage *pMsgUnixUser = dbus_message_new_method_call(CK_INTERFACE,
981 *ppszCurSession,
982 CK_SESSION_INTERFACE,
983 "GetUnixUser");
984 if ( fActive
985 && pMsgUnixUser
986 && dbus_message_get_type(pMsgUnixUser) == DBUS_MESSAGE_TYPE_METHOD_CALL)
987 {
988 DBusMessage *pReplyUnixUser = dbus_connection_send_with_reply_and_block(pConnection,
989 pMsgUnixUser,
990 30 * 1000 /* 30s timeout */,
991 &dbErr);
992 if ( pReplyUnixUser
993 && !dbus_error_is_set(&dbErr))
994 {
995 DBusMessageIter itMsg;
996 if ( dbus_message_iter_init(pReplyUnixUser, &itMsg)
997 && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_UINT32)
998 {
999 /* Get uid from message. */
1000 uint32_t uid;
1001 dbus_message_iter_get_basic(&itMsg, &uid);
1002
1003 /* Look up user name (realname) from uid. */
1004 setpwent();
1005 struct passwd *ppwEntry = getpwuid(uid);
1006 if ( ppwEntry
1007 && ppwEntry->pw_name)
1008 {
1009 VGSvcVerbose(4, "ConsoleKit: session '%s' -> %s (uid: %RU32)\n",
1010 *ppszCurSession, ppwEntry->pw_name, uid);
1011 vgsvcVMInfoAddUserToList(ppwEntry->pw_name, "ConsoleKit");
1012 }
1013 else
1014 VGSvcError("ConsoleKit: unable to lookup user name for uid=%RU32\n", uid);
1015 }
1016 else
1017 AssertMsgFailed(("ConsoleKit: GetUnixUser returned a wrong argument type\n"));
1018 }
1019 /** @todo clean up if &dbErr */
1020
1021 vboxService_dbus_message_discard(&pReplyUnixUser);
1022 }
1023 else if (fActive) /* don't bitch about inactive users */
1024 {
1025 static int s_iBitchedAboutConsoleKit = 0;
1026 if (s_iBitchedAboutConsoleKit < 1)
1027 {
1028 s_iBitchedAboutConsoleKit++;
1029 VGSvcError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s\n",
1030 *ppszCurSession, dbus_message_get_type(pMsgUnixUser),
1031 dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
1032 }
1033 }
1034
1035 vboxService_dbus_message_discard(&pMsgUnixUser);
1036 }
1037
1038 dbus_free_string_array(ppszSessions);
1039 }
1040 else
1041 VGSvcError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s\n",
1042 dbus_message_get_type(pMsgSessions),
1043 dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
1044 vboxService_dbus_message_discard(&pReplySessions);
1045 }
1046 }
1047 else
1048 {
1049 static int s_iBitchedAboutConsoleKit = 0;
1050 if (s_iBitchedAboutConsoleKit < 3)
1051 {
1052 s_iBitchedAboutConsoleKit++;
1053 VGSvcError("Unable to invoke ConsoleKit (%d/3) -- maybe not installed / used? Error: %s\n",
1054 s_iBitchedAboutConsoleKit,
1055 dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
1056 }
1057 }
1058
1059 vboxService_dbus_message_discard(&pMsgSessions);
1060 }
1061 else
1062 {
1063 static int s_iBitchedAboutDBus = 0;
1064 if (s_iBitchedAboutDBus < 3)
1065 {
1066 s_iBitchedAboutDBus++;
1067 VGSvcError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus,
1068 fHaveLibDbus && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed");
1069 }
1070 }
1071
1072 if ( fHaveLibDbus
1073 && dbus_error_is_set(&dbErr))
1074 dbus_error_free(&dbErr);
1075# endif /* RT_OS_LINUX */
1076# endif /* VBOX_WITH_DBUS */
1077
1078 /* Calc the string length. */
1079 size_t cchUserList = 0;
1080 if (RT_SUCCESS(rc))
1081 for (uint32_t i = 0; i < cUsersInList; i++)
1082 cchUserList += (i != 0) + strlen(papszUsers[i]);
1083
1084 /* Build the user list. */
1085 if (cchUserList > 0)
1086 {
1087 if (RT_SUCCESS(rc))
1088 rc = RTStrAllocEx(&pszUserList, cchUserList + 1);
1089 if (RT_SUCCESS(rc))
1090 {
1091 char *psz = pszUserList;
1092 for (uint32_t i = 0; i < cUsersInList; i++)
1093 {
1094 if (i != 0)
1095 *psz++ = ',';
1096 size_t cch = strlen(papszUsers[i]);
1097 memcpy(psz, papszUsers[i], cch);
1098 psz += cch;
1099 }
1100 *psz = '\0';
1101 }
1102 }
1103
1104 /* Cleanup. */
1105 for (uint32_t i = 0; i < cUsersInList; i++)
1106 RTStrFree(papszUsers[i]);
1107 RTMemFree(papszUsers);
1108
1109 endutxent(); /* Close utmpx file. */
1110#endif /* !RT_OS_WINDOWS && !RT_OS_FREEBSD && !RT_OS_HAIKU && !RT_OS_OS2 */
1111
1112 Assert(RT_FAILURE(rc) || cUsersInList == 0 || (pszUserList && *pszUserList));
1113
1114 /*
1115 * If the user enumeration above failed, reset the user count to 0 except
1116 * we didn't have enough memory anymore. In that case we want to preserve
1117 * the previous user count in order to not confuse third party tools which
1118 * rely on that count.
1119 */
1120 if (RT_FAILURE(rc))
1121 {
1122 if (rc == VERR_NO_MEMORY)
1123 {
1124 static int s_iVMInfoBitchedOOM = 0;
1125 if (s_iVMInfoBitchedOOM++ < 3)
1126 VGSvcVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%RU32)\n",
1127 g_cVMInfoLoggedInUsers);
1128 cUsersInList = g_cVMInfoLoggedInUsers;
1129 }
1130 else
1131 cUsersInList = 0;
1132 }
1133 else /* Preserve logged in users count. */
1134 g_cVMInfoLoggedInUsers = cUsersInList;
1135
1136 VGSvcVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc);
1137
1138 if (pszUserList)
1139 {
1140 AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n"));
1141 rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList);
1142 }
1143 else
1144 rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL);
1145 if (RT_FAILURE(rc))
1146 VGSvcError("Error writing logged in users list, rc=%Rrc\n", rc);
1147
1148 rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList);
1149 if (RT_FAILURE(rc))
1150 VGSvcError("Error writing logged in users count, rc=%Rrc\n", rc);
1151
1152/** @todo r=bird: What's this 'beacon' nonsense here? It's _not_ defined with
1153 * the VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE flag set!! */
1154 rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, cUsersInList == 0 ? "true" : "false");
1155 if (RT_FAILURE(rc))
1156 VGSvcError("Error writing no logged in users beacon, rc=%Rrc\n", rc);
1157
1158 if (pszUserList)
1159 RTStrFree(pszUserList);
1160
1161 VGSvcVerbose(4, "Writing users returned with rc=%Rrc\n", rc);
1162 return rc;
1163}
1164
1165
1166/**
1167 * Provide information about the guest network.
1168 */
1169static int vgsvcVMInfoWriteNetwork(void)
1170{
1171 uint32_t cIfsReported = 0;
1172 char szPropPath[256];
1173
1174#ifdef RT_OS_WINDOWS
1175 /*
1176 * Check that the APIs we need are present.
1177 */
1178 if ( !g_pfnWSAIoctl
1179 || !g_pfnWSASocketA
1180 || !g_pfnWSAGetLastError
1181 || !g_pfninet_ntoa
1182 || !g_pfnclosesocket)
1183 return VINF_SUCCESS;
1184
1185 /*
1186 * Query the IP adapter info first, if we have the API.
1187 */
1188 IP_ADAPTER_INFO *pAdpInfo = NULL;
1189 if (g_pfnGetAdaptersInfo)
1190 {
1191 ULONG cbAdpInfo = RT_MAX(sizeof(IP_ADAPTER_INFO) * 2, _2K);
1192 pAdpInfo = (IP_ADAPTER_INFO *)RTMemAllocZ(cbAdpInfo);
1193 if (!pAdpInfo)
1194 {
1195 VGSvcError("VMInfo/Network: Failed to allocate two IP_ADAPTER_INFO structures\n");
1196 return VERR_NO_MEMORY;
1197 }
1198
1199 DWORD dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
1200 if (dwRet == ERROR_BUFFER_OVERFLOW)
1201 {
1202 IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo);
1203 if (pAdpInfoNew)
1204 {
1205 pAdpInfo = pAdpInfoNew;
1206 RT_BZERO(pAdpInfo, cbAdpInfo);
1207 dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
1208 }
1209 }
1210 if (dwRet != NO_ERROR)
1211 {
1212 RTMemFree(pAdpInfo);
1213 pAdpInfo = NULL;
1214 if (dwRet == ERROR_NO_DATA)
1215 /* If no network adapters available / present in the
1216 system we pretend success to not bail out too early. */
1217 VGSvcVerbose(3, "VMInfo/Network: No network adapters present according to GetAdaptersInfo.\n");
1218 else
1219 {
1220 VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet);
1221 return RTErrConvertFromWin32(dwRet);
1222 }
1223 }
1224 }
1225
1226 /*
1227 * Ask the TCP/IP stack for an interface list.
1228 */
1229 SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
1230 if (sd == SOCKET_ERROR) /* Socket invalid. */
1231 {
1232 int const wsaErr = g_pfnWSAGetLastError();
1233 RTMemFree(pAdpInfo);
1234
1235 /* Don't complain/bail out with an error if network stack is not up; can happen
1236 * on NT4 due to start up when not connected shares dialogs pop up. */
1237 if (wsaErr == WSAENETDOWN)
1238 {
1239 VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n");
1240 return VINF_SUCCESS;
1241 }
1242 VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr);
1243 return RTErrConvertFromWin32(wsaErr);
1244 }
1245
1246 INTERFACE_INFO aInterfaces[20] = {{0}};
1247 DWORD cbReturned = 0;
1248# ifdef RT_ARCH_X86
1249 /* Workaround for uninitialized variable used in memcpy in GetTcpipInterfaceList
1250 (NT4SP1 at least). It seems to be happy enough with garbages, no failure
1251 returns so far, so we just need to prevent it from crashing by filling the
1252 stack with valid pointer values prior to the API call. */
1253 _asm
1254 {
1255 mov edx, edi
1256 lea eax, aInterfaces
1257 mov [esp - 0x1000], eax
1258 mov [esp - 0x2000], eax
1259 mov ecx, 0x2000/4 - 1
1260 cld
1261 lea edi, [esp - 0x2000]
1262 rep stosd
1263 mov edi, edx
1264 }
1265# endif
1266 int rc = g_pfnWSAIoctl(sd,
1267 SIO_GET_INTERFACE_LIST,
1268 NULL, /* pvInBuffer */
1269 0, /* cbInBuffer */
1270 &aInterfaces[0], /* pvOutBuffer */
1271 sizeof(aInterfaces), /* cbOutBuffer */
1272 &cbReturned,
1273 NULL, /* pOverlapped */
1274 NULL); /* pCompletionRoutine */
1275 if (rc == SOCKET_ERROR)
1276 {
1277 VGSvcError("VMInfo/Network: Failed to WSAIoctl() on socket: Error: %d\n", g_pfnWSAGetLastError());
1278 RTMemFree(pAdpInfo);
1279 g_pfnclosesocket(sd);
1280 return RTErrConvertFromWin32(g_pfnWSAGetLastError());
1281 }
1282 g_pfnclosesocket(sd);
1283 int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO);
1284
1285 /*
1286 * Iterate the inteface list we got back from the TCP/IP,
1287 * using the pAdpInfo list to supply the MAC address.
1288 */
1289 /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */
1290 for (int i = 0; i < cIfacesSystem; ++i)
1291 {
1292 if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */
1293 continue;
1294 sockaddr_in *pAddress = &aInterfaces[i].iiAddress.AddressIn;
1295 char szIp[32];
1296 RTStrPrintf(szIp, sizeof(szIp), "%s", g_pfninet_ntoa(pAddress->sin_addr));
1297 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
1298 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp);
1299
1300 pAddress = &aInterfaces[i].iiBroadcastAddress.AddressIn;
1301 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
1302 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
1303
1304 pAddress = (sockaddr_in *)&aInterfaces[i].iiNetmask;
1305 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
1306 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
1307
1308 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
1309 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, aInterfaces[i].iiFlags & IFF_UP ? "Up" : "Down");
1310
1311 if (pAdpInfo)
1312 {
1313 IP_ADAPTER_INFO *pAdp;
1314 for (pAdp = pAdpInfo; pAdp; pAdp = pAdp->Next)
1315 if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp))
1316 break;
1317
1318 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
1319 if (pAdp)
1320 {
1321 char szMac[32];
1322 RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
1323 pAdp->Address[0], pAdp->Address[1], pAdp->Address[2],
1324 pAdp->Address[3], pAdp->Address[4], pAdp->Address[5]);
1325 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
1326 }
1327 else
1328 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, NULL);
1329 }
1330
1331 cIfsReported++;
1332 }
1333
1334 RTMemFree(pAdpInfo);
1335
1336#elif defined(RT_OS_HAIKU)
1337 /** @todo Haiku: implement network info. retreival */
1338 return VERR_NOT_IMPLEMENTED;
1339
1340#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
1341 struct ifaddrs *pIfHead = NULL;
1342
1343 /* Get all available interfaces */
1344 int rc = getifaddrs(&pIfHead);
1345 if (rc < 0)
1346 {
1347 rc = RTErrConvertFromErrno(errno);
1348 VGSvcError("VMInfo/Network: Failed to get all interfaces: Error %Rrc\n");
1349 return rc;
1350 }
1351
1352 /* Loop through all interfaces and set the data. */
1353 for (struct ifaddrs *pIfCurr = pIfHead; pIfCurr; pIfCurr = pIfCurr->ifa_next)
1354 {
1355 /*
1356 * Only AF_INET and no loopback interfaces
1357 */
1358 /** @todo IPv6 interfaces */
1359 if ( pIfCurr->ifa_addr->sa_family == AF_INET
1360 && !(pIfCurr->ifa_flags & IFF_LOOPBACK))
1361 {
1362 char szInetAddr[NI_MAXHOST];
1363
1364 memset(szInetAddr, 0, NI_MAXHOST);
1365 getnameinfo(pIfCurr->ifa_addr, sizeof(struct sockaddr_in),
1366 szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
1367 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
1368 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
1369
1370 memset(szInetAddr, 0, NI_MAXHOST);
1371 getnameinfo(pIfCurr->ifa_broadaddr, sizeof(struct sockaddr_in),
1372 szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
1373 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
1374 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
1375
1376 memset(szInetAddr, 0, NI_MAXHOST);
1377 getnameinfo(pIfCurr->ifa_netmask, sizeof(struct sockaddr_in),
1378 szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
1379 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
1380 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
1381
1382 /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */
1383 for (struct ifaddrs *pIfLinkCurr = pIfHead; pIfLinkCurr; pIfLinkCurr = pIfLinkCurr->ifa_next)
1384 {
1385 if ( pIfLinkCurr->ifa_addr->sa_family == AF_LINK
1386 && !strcmp(pIfCurr->ifa_name, pIfLinkCurr->ifa_name))
1387 {
1388 char szMac[32];
1389 uint8_t *pu8Mac = NULL;
1390 struct sockaddr_dl *pLinkAddress = (struct sockaddr_dl *)pIfLinkCurr->ifa_addr;
1391
1392 AssertPtr(pLinkAddress);
1393 pu8Mac = (uint8_t *)LLADDR(pLinkAddress);
1394 RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
1395 pu8Mac[0], pu8Mac[1], pu8Mac[2], pu8Mac[3], pu8Mac[4], pu8Mac[5]);
1396 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
1397 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
1398 break;
1399 }
1400 }
1401
1402 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
1403 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down");
1404
1405 cIfsReported++;
1406 }
1407 }
1408
1409 /* Free allocated resources. */
1410 freeifaddrs(pIfHead);
1411
1412#else /* !RT_OS_WINDOWS && !RT_OS_FREEBSD */
1413 /*
1414 * Use SIOCGIFCONF to get a list of interface/protocol configurations.
1415 *
1416 * See "UNIX Network Programming Volume 1" by W. R. Stevens, section 17.6
1417 * for details on this ioctl.
1418 */
1419 int sd = socket(AF_INET, SOCK_DGRAM, 0);
1420 if (sd < 0)
1421 {
1422 int rc = RTErrConvertFromErrno(errno);
1423 VGSvcError("VMInfo/Network: Failed to get a socket: Error %Rrc\n", rc);
1424 return rc;
1425 }
1426
1427 /* Call SIOCGIFCONF with the right sized buffer (remember the size). */
1428 static int s_cbBuf = 256; // 1024
1429 int cbBuf = s_cbBuf;
1430 char *pchBuf;
1431 struct ifconf IfConf;
1432 int rc = VINF_SUCCESS;
1433 for (;;)
1434 {
1435 pchBuf = (char *)RTMemTmpAllocZ(cbBuf);
1436 if (!pchBuf)
1437 {
1438 rc = VERR_NO_TMP_MEMORY;
1439 break;
1440 }
1441
1442 IfConf.ifc_len = cbBuf;
1443 IfConf.ifc_buf = pchBuf;
1444 if (ioctl(sd, SIOCGIFCONF, &IfConf) >= 0)
1445 {
1446 /* Hard to anticipate how space an address might possibly take, so
1447 making some generous assumptions here to avoid performing the
1448 query twice with different buffer sizes. */
1449 if (IfConf.ifc_len + 128 < cbBuf)
1450 break;
1451 }
1452 else if (errno != EOVERFLOW)
1453 {
1454 rc = RTErrConvertFromErrno(errno);
1455 break;
1456 }
1457
1458 /* grow the buffer */
1459 s_cbBuf = cbBuf *= 2;
1460 RTMemFree(pchBuf);
1461 }
1462 if (RT_FAILURE(rc))
1463 {
1464 close(sd);
1465 RTMemTmpFree(pchBuf);
1466 VGSvcError("VMInfo/Network: Error doing SIOCGIFCONF (cbBuf=%d): %Rrc\n", cbBuf, rc);
1467 return rc;
1468 }
1469
1470 /*
1471 * Iterate the interface/protocol configurations.
1472 *
1473 * Note! The current code naively assumes one IPv4 address per interface.
1474 * This means that guest assigning more than one address to an
1475 * interface will get multiple entries for one physical interface.
1476 */
1477# ifdef RT_OS_OS2
1478 struct ifreq *pPrevLinkAddr = NULL;
1479# endif
1480 struct ifreq *pCur = IfConf.ifc_req;
1481 size_t cbLeft = IfConf.ifc_len;
1482 while (cbLeft >= sizeof(*pCur))
1483 {
1484# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
1485 /* These two do not provide the sa_len member but only support address
1486 * families which do not need extra bytes on the end. */
1487# define SA_LEN(pAddr) sizeof(struct sockaddr)
1488# elif !defined(SA_LEN)
1489# define SA_LEN(pAddr) (pAddr)->sa_len
1490# endif
1491 /* Figure the size of the current request. */
1492 size_t cbCur = RT_UOFFSETOF(struct ifreq, ifr_addr)
1493 + SA_LEN(&pCur->ifr_addr);
1494 cbCur = RT_MAX(cbCur, sizeof(struct ifreq));
1495# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
1496 Assert(pCur->ifr_addr.sa_family == AF_INET);
1497# endif
1498 AssertBreak(cbCur <= cbLeft);
1499
1500# ifdef RT_OS_OS2
1501 /* On OS/2 we get the MAC address in the AF_LINK that the BSD 4.4 stack
1502 emits. We boldly ASSUME these always comes first. */
1503 if ( pCur->ifr_addr.sa_family == AF_LINK
1504 && ((struct sockaddr_dl *)&pCur->ifr_addr)->sdl_alen == 6)
1505 pPrevLinkAddr = pCur;
1506# endif
1507
1508 /* Skip it if it's not the kind of address we're looking for. */
1509 struct ifreq IfReqTmp;
1510 bool fIfUp = false;
1511 bool fSkip = false;
1512 if (pCur->ifr_addr.sa_family != AF_INET)
1513 fSkip = true;
1514 else
1515 {
1516 /* Get the interface flags so we can detect loopback and check if it's up. */
1517 IfReqTmp = *pCur;
1518 if (ioctl(sd, SIOCGIFFLAGS, &IfReqTmp) < 0)
1519 {
1520 rc = RTErrConvertFromErrno(errno);
1521 VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFFLAGS,%s) on socket: Error %Rrc\n", pCur->ifr_name, rc);
1522 break;
1523 }
1524 fIfUp = !!(IfReqTmp.ifr_flags & IFF_UP);
1525 if (IfReqTmp.ifr_flags & IFF_LOOPBACK) /* Skip the loopback device. */
1526 fSkip = true;
1527 }
1528 if (!fSkip)
1529 {
1530 size_t offSubProp = RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32", cIfsReported);
1531
1532 sockaddr_in *pAddress = (sockaddr_in *)&pCur->ifr_addr;
1533 strcpy(&szPropPath[offSubProp], "/V4/IP");
1534 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
1535
1536 /* Get the broadcast address. */
1537 IfReqTmp = *pCur;
1538 if (ioctl(sd, SIOCGIFBRDADDR, &IfReqTmp) < 0)
1539 {
1540 rc = RTErrConvertFromErrno(errno);
1541 VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFBRDADDR) on socket: Error %Rrc\n", rc);
1542 break;
1543 }
1544 pAddress = (sockaddr_in *)&IfReqTmp.ifr_broadaddr;
1545 strcpy(&szPropPath[offSubProp], "/V4/Broadcast");
1546 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
1547
1548 /* Get the net mask. */
1549 IfReqTmp = *pCur;
1550 if (ioctl(sd, SIOCGIFNETMASK, &IfReqTmp) < 0)
1551 {
1552 rc = RTErrConvertFromErrno(errno);
1553 VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFNETMASK) on socket: Error %Rrc\n", rc);
1554 break;
1555 }
1556# if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
1557 pAddress = (sockaddr_in *)&IfReqTmp.ifr_addr;
1558# else
1559 pAddress = (sockaddr_in *)&IfReqTmp.ifr_netmask;
1560# endif
1561 strcpy(&szPropPath[offSubProp], "/V4/Netmask");
1562 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
1563
1564# if defined(RT_OS_SOLARIS)
1565 /*
1566 * "ifreq" is obsolete on Solaris. We use the recommended "lifreq".
1567 * We might fail if the interface has not been assigned an IP address.
1568 * That doesn't matter; as long as it's plumbed we can pick it up.
1569 * But, if it has not acquired an IP address we cannot obtain it's MAC
1570 * address this way, so we just use all zeros there.
1571 */
1572 RTMAC IfMac;
1573 struct lifreq IfReq;
1574 RT_ZERO(IfReq);
1575 AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name));
1576 strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name));
1577 if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0)
1578 {
1579 struct arpreq ArpReq;
1580 RT_ZERO(ArpReq);
1581 memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in));
1582
1583 if (ioctl(sd, SIOCGARP, &ArpReq) >= 0)
1584 memcpy(&IfMac, ArpReq.arp_ha.sa_data, sizeof(IfMac));
1585 else
1586 {
1587 rc = RTErrConvertFromErrno(errno);
1588 VGSvcError("VMInfo/Network: failed to ioctl(SIOCGARP) on socket: Error %Rrc\n", rc);
1589 break;
1590 }
1591 }
1592 else
1593 {
1594 VGSvcVerbose(2, "VMInfo/Network: Interface '%s' has no assigned IP address, skipping ...\n", pCur->ifr_name);
1595 continue;
1596 }
1597# elif defined(RT_OS_OS2)
1598 RTMAC IfMac;
1599 if ( pPrevLinkAddr
1600 && strncmp(pCur->ifr_name, pPrevLinkAddr->ifr_name, sizeof(pCur->ifr_name)) == 0)
1601 {
1602 struct sockaddr_dl *pDlAddr = (struct sockaddr_dl *)&pPrevLinkAddr->ifr_addr;
1603 IfMac = *(PRTMAC)&pDlAddr->sdl_data[pDlAddr->sdl_nlen];
1604 }
1605 else
1606 RT_ZERO(IfMac);
1607#else
1608 if (ioctl(sd, SIOCGIFHWADDR, &IfReqTmp) < 0)
1609 {
1610 rc = RTErrConvertFromErrno(errno);
1611 VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFHWADDR) on socket: Error %Rrc\n", rc);
1612 break;
1613 }
1614 RTMAC IfMac = *(PRTMAC)&IfReqTmp.ifr_hwaddr.sa_data[0];
1615# endif
1616 strcpy(&szPropPath[offSubProp], "/MAC");
1617 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%02X%02X%02X%02X%02X%02X",
1618 IfMac.au8[0], IfMac.au8[1], IfMac.au8[2], IfMac.au8[3], IfMac.au8[4], IfMac.au8[5]);
1619
1620 strcpy(&szPropPath[offSubProp], "/Status");
1621 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down");
1622
1623 /* The name. */
1624 int rc2 = RTStrValidateEncodingEx(pCur->ifr_name, sizeof(pCur->ifr_name), 0);
1625 if (RT_SUCCESS(rc2))
1626 {
1627 strcpy(&szPropPath[offSubProp], "/Name");
1628 VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%.*s", sizeof(pCur->ifr_name), pCur->ifr_name);
1629 }
1630
1631 cIfsReported++;
1632 }
1633
1634 /*
1635 * Next interface/protocol configuration.
1636 */
1637 pCur = (struct ifreq *)((uintptr_t)pCur + cbCur);
1638 cbLeft -= cbCur;
1639 }
1640
1641 RTMemTmpFree(pchBuf);
1642 close(sd);
1643 if (RT_FAILURE(rc))
1644 VGSvcError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfsReported, rc);
1645
1646#endif /* !RT_OS_WINDOWS */
1647
1648#if 0 /* Zapping not enabled yet, needs more testing first. */
1649 /*
1650 * Zap all stale network interface data if the former (saved) network ifaces count
1651 * is bigger than the current one.
1652 */
1653
1654 /* Get former count. */
1655 uint32_t cIfsReportedOld;
1656 rc = VGSvcReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfsReportedOld,
1657 0 /* Min */, UINT32_MAX /* Max */);
1658 if ( RT_SUCCESS(rc)
1659 && cIfsReportedOld > cIfsReported) /* Are some ifaces not around anymore? */
1660 {
1661 VGSvcVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n",
1662 cIfsReportedOld, cIfsReported);
1663
1664 uint32_t uIfaceDeleteIdx = cIfsReported;
1665 do
1666 {
1667 VGSvcVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx);
1668 rc = VGSvcPropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++);
1669 } while (RT_SUCCESS(rc));
1670 }
1671 else if ( RT_FAILURE(rc)
1672 && rc != VERR_NOT_FOUND)
1673 {
1674 VGSvcError("VMInfo/Network: Failed retrieving old network interfaces count with error %Rrc\n", rc);
1675 }
1676#endif
1677
1678 /*
1679 * This property is a beacon which is _always_ written, even if the network configuration
1680 * does not change. If this property is missing, the host assumes that all other GuestInfo
1681 * properties are no longer valid.
1682 */
1683 VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfsReported);
1684
1685 /* Don't fail here; just report everything we got. */
1686 return VINF_SUCCESS;
1687}
1688
1689
1690/**
1691 * @interface_method_impl{VBOXSERVICE,pfnWorker}
1692 */
1693static DECLCALLBACK(int) vbsvcVMInfoWorker(bool volatile *pfShutdown)
1694{
1695 int rc;
1696
1697 /*
1698 * Tell the control thread that it can continue
1699 * spawning services.
1700 */
1701 RTThreadUserSignal(RTThreadSelf());
1702
1703#ifdef RT_OS_WINDOWS
1704 /* Required for network information (must be called per thread). */
1705 if (g_pfnWSAStartup)
1706 {
1707 WSADATA wsaData;
1708 RT_ZERO(wsaData);
1709 if (g_pfnWSAStartup(MAKEWORD(2, 2), &wsaData))
1710 VGSvcError("VMInfo/Network: WSAStartup failed! Error: %Rrc\n", RTErrConvertFromWin32(g_pfnWSAGetLastError()));
1711 }
1712#endif
1713
1714 /*
1715 * Write the fixed properties first.
1716 */
1717 vgsvcVMInfoWriteFixedProperties();
1718
1719 /*
1720 * Now enter the loop retrieving runtime data continuously.
1721 */
1722 for (;;)
1723 {
1724 rc = vgsvcVMInfoWriteUsers();
1725 if (RT_FAILURE(rc))
1726 break;
1727
1728 rc = vgsvcVMInfoWriteNetwork();
1729 if (RT_FAILURE(rc))
1730 break;
1731
1732 /* Whether to wait for event semaphore or not. */
1733 bool fWait = true;
1734
1735 /* Check for location awareness. This most likely only
1736 * works with VBox (latest) 4.1 and up. */
1737
1738 /* Check for new connection. */
1739 char *pszLAClientID = NULL;
1740 int rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, g_pszLAActiveClient, true /* Read only */,
1741 &pszLAClientID, NULL /* Flags */, NULL /* Timestamp */);
1742 if (RT_SUCCESS(rc2))
1743 {
1744 AssertPtr(pszLAClientID);
1745 if (RTStrICmp(pszLAClientID, "0")) /* Is a client connected? */
1746 {
1747 uint32_t uLAClientID = RTStrToInt32(pszLAClientID);
1748 uint64_t uLAClientAttachedTS;
1749
1750 /* Peek at "Attach" value to figure out if hotdesking happened. */
1751 char *pszAttach = NULL;
1752 rc2 = vgsvcGetLAClientValue(uLAClientID, "Attach", &pszAttach,
1753 &uLAClientAttachedTS);
1754
1755 if ( RT_SUCCESS(rc2)
1756 && ( !g_LAClientAttachedTS
1757 || (g_LAClientAttachedTS != uLAClientAttachedTS)))
1758 {
1759 vgsvcFreeLAClientInfo(&g_LAClientInfo);
1760
1761 /* Note: There is a race between setting the guest properties by the host and getting them by
1762 * the guest. */
1763 rc2 = vgsvcGetLAClientInfo(uLAClientID, &g_LAClientInfo);
1764 if (RT_SUCCESS(rc2))
1765 {
1766 VGSvcVerbose(1, "VRDP: Hotdesk client %s with ID=%RU32, Name=%s, Domain=%s\n",
1767 /* If g_LAClientAttachedTS is 0 this means there already was an active
1768 * hotdesk session when VBoxService started. */
1769 !g_LAClientAttachedTS ? "already active" : g_LAClientInfo.fAttached ? "connected" : "disconnected",
1770 uLAClientID, g_LAClientInfo.pszName, g_LAClientInfo.pszDomain);
1771
1772 g_LAClientAttachedTS = g_LAClientInfo.uAttachedTS;
1773
1774 /* Don't wait for event semaphore below anymore because we now know that the client
1775 * changed. This means we need to iterate all VM information again immediately. */
1776 fWait = false;
1777 }
1778 else
1779 {
1780 static int s_iBitchedAboutLAClientInfo = 0;
1781 if (s_iBitchedAboutLAClientInfo < 10)
1782 {
1783 s_iBitchedAboutLAClientInfo++;
1784 VGSvcError("Error getting active location awareness client info, rc=%Rrc\n", rc2);
1785 }
1786 }
1787 }
1788 else if (RT_FAILURE(rc2))
1789 VGSvcError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", uLAClientID, rc2);
1790 if (pszAttach)
1791 RTStrFree(pszAttach);
1792 }
1793 else
1794 {
1795 VGSvcVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n");
1796 vgsvcFreeLAClientInfo(&g_LAClientInfo);
1797 }
1798
1799 RTStrFree(pszLAClientID);
1800 }
1801 else
1802 {
1803 static int s_iBitchedAboutLAClient = 0;
1804 if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */
1805 && s_iBitchedAboutLAClient < 3)
1806 {
1807 s_iBitchedAboutLAClient++;
1808 VGSvcError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2);
1809 }
1810 }
1811
1812 VGSvcVerbose(3, "VRDP: Handling location awareness done\n");
1813
1814 /*
1815 * Flush all properties if we were restored.
1816 */
1817 uint64_t idNewSession = g_idVMInfoSession;
1818 VbglR3GetSessionId(&idNewSession);
1819 if (idNewSession != g_idVMInfoSession)
1820 {
1821 VGSvcVerbose(3, "The VM session ID changed, flushing all properties\n");
1822 vgsvcVMInfoWriteFixedProperties();
1823 VGSvcPropCacheFlush(&g_VMInfoPropCache);
1824 g_idVMInfoSession = idNewSession;
1825 }
1826
1827 /*
1828 * Block for a while.
1829 *
1830 * The event semaphore takes care of ignoring interruptions and it
1831 * allows us to implement service wakeup later.
1832 */
1833 if (*pfShutdown)
1834 break;
1835 if (fWait)
1836 rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval);
1837 if (*pfShutdown)
1838 break;
1839 if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
1840 {
1841 VGSvcError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
1842 rc = rc2;
1843 break;
1844 }
1845 else if (RT_LIKELY(RT_SUCCESS(rc2)))
1846 {
1847 /* Reset event semaphore if it got triggered. */
1848 rc2 = RTSemEventMultiReset(g_hVMInfoEvent);
1849 if (RT_FAILURE(rc2))
1850 rc2 = VGSvcError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2);
1851 }
1852 }
1853
1854#ifdef RT_OS_WINDOWS
1855 if (g_pfnWSACleanup)
1856 g_pfnWSACleanup();
1857#endif
1858
1859 return rc;
1860}
1861
1862
1863/**
1864 * @interface_method_impl{VBOXSERVICE,pfnStop}
1865 */
1866static DECLCALLBACK(void) vbsvcVMInfoStop(void)
1867{
1868 RTSemEventMultiSignal(g_hVMInfoEvent);
1869}
1870
1871
1872/**
1873 * @interface_method_impl{VBOXSERVICE,pfnTerm}
1874 */
1875static DECLCALLBACK(void) vbsvcVMInfoTerm(void)
1876{
1877 if (g_hVMInfoEvent != NIL_RTSEMEVENTMULTI)
1878 {
1879 /** @todo temporary solution: Zap all values which are not valid
1880 * anymore when VM goes down (reboot/shutdown ). Needs to
1881 * be replaced with "temporary properties" later.
1882 *
1883 * One idea is to introduce a (HGCM-)session guest property
1884 * flag meaning that a guest property is only valid as long
1885 * as the HGCM session isn't closed (e.g. guest application
1886 * terminates). [don't remove till implemented]
1887 */
1888 /** @todo r=bird: Drop the VbglR3GuestPropDelSet call here and use the cache
1889 * since it remembers what we've written. */
1890 /* Delete the "../Net" branch. */
1891 const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" };
1892 int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat));
1893
1894 /* Destroy LA client info. */
1895 vgsvcFreeLAClientInfo(&g_LAClientInfo);
1896
1897 /* Destroy property cache. */
1898 VGSvcPropCacheDestroy(&g_VMInfoPropCache);
1899
1900 /* Disconnect from guest properties service. */
1901 rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID);
1902 if (RT_FAILURE(rc))
1903 VGSvcError("Failed to disconnect from guest property service! Error: %Rrc\n", rc);
1904 g_uVMInfoGuestPropSvcClientID = 0;
1905
1906 RTSemEventMultiDestroy(g_hVMInfoEvent);
1907 g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
1908 }
1909}
1910
1911
1912/**
1913 * The 'vminfo' service description.
1914 */
1915VBOXSERVICE g_VMInfo =
1916{
1917 /* pszName. */
1918 "vminfo",
1919 /* pszDescription. */
1920 "Virtual Machine Information",
1921 /* pszUsage. */
1922 " [--vminfo-interval <ms>] [--vminfo-user-idle-threshold <ms>]"
1923 ,
1924 /* pszOptions. */
1925 " --vminfo-interval Specifies the interval at which to retrieve the\n"
1926 " VM information. The default is 10000 ms.\n"
1927 " --vminfo-user-idle-threshold <ms>\n"
1928 " Specifies the user idle threshold (in ms) for\n"
1929 " considering a guest user as being idle. The default\n"
1930 " is 5000 (5 seconds).\n"
1931 ,
1932 /* methods */
1933 vbsvcVMInfoPreInit,
1934 vbsvcVMInfoOption,
1935 vbsvcVMInfoInit,
1936 vbsvcVMInfoWorker,
1937 vbsvcVMInfoStop,
1938 vbsvcVMInfoTerm
1939};
1940
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use