VirtualBox

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

Last change on this file was 103149, checked in by vboxsync, 3 months ago

Additions: Some warning fixes about externally visible functions which should be static, bugref:3409

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

© 2023 Oracle
ContactPrivacy policyTerms of Use