VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette