VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp@ 30121

Last change on this file since 30121 was 30049, checked in by vboxsync, 15 years ago

VBoxServiceVMInfo-win.cpp: r=bird: Consistent error handling in VBoxServiceVMInfoWinWriteUsers.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.4 KB
Line 
1/* $Id: VBoxServiceVMInfo-win.cpp 30049 2010-06-07 06:36:26Z vboxsync $ */
2/** @file
3 * VBoxService - Virtual Machine Information for the Host, Windows specifics.
4 */
5
6/*
7 * Copyright (C) 2009-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include <windows.h>
23#include <wtsapi32.h> /* For WTS* calls. */
24#include <psapi.h> /* EnumProcesses. */
25#include <Ntsecapi.h> /* Needed for process security information. */
26
27#include <iprt/assert.h>
28#include <iprt/mem.h>
29#include <iprt/thread.h>
30#include <iprt/string.h>
31#include <iprt/semaphore.h>
32#include <iprt/system.h>
33#include <iprt/time.h>
34#include <VBox/VBoxGuestLib.h>
35#include "VBoxServiceInternal.h"
36#include "VBoxServiceUtils.h"
37
38
39/*******************************************************************************
40* Structures and Typedefs *
41*******************************************************************************/
42/** Structure for storing the looked up user information. */
43typedef struct
44{
45 WCHAR wszUser[_MAX_PATH];
46 WCHAR wszAuthenticationPackage[_MAX_PATH];
47 WCHAR wszLogonDomain[_MAX_PATH];
48} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
49
50/** Structure for the file information lookup. */
51typedef struct
52{
53 char *pszFilePath;
54 char *pszFileName;
55} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
56
57/** Structure for process information lookup. */
58typedef struct
59{
60 DWORD id;
61 LUID luid;
62} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC;
63
64
65/*******************************************************************************
66* Prototypes
67*******************************************************************************/
68bool VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, VBOXSERVICEVMINFOPROC const *paProcs, DWORD cProcs);
69bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
70int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
71void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs);
72
73
74
75#ifndef TARGET_NT4
76
77/**
78 * Fills in more data for a process.
79 *
80 * @returns VBox status code.
81 * @param pProc The process structure to fill data into.
82 * @param tkClass The kind of token information to get.
83 */
84static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc,
85 TOKEN_INFORMATION_CLASS tkClass)
86{
87 AssertPtr(pProc);
88 HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id);
89 if (h == NULL)
90 return RTErrConvertFromWin32(GetLastError());
91
92 int rc = VERR_NO_MEMORY;
93 HANDLE hToken;
94 if (OpenProcessToken(h, TOKEN_QUERY, &hToken))
95 {
96 void *pvTokenInfo = NULL;
97 DWORD dwTokenInfoSize;
98 switch (tkClass)
99 {
100 case TokenStatistics:
101 dwTokenInfoSize = sizeof(TOKEN_STATISTICS);
102 pvTokenInfo = RTMemAlloc(dwTokenInfoSize);
103 break;
104
105 /** @todo Implement more token classes here. */
106
107 default:
108 VBoxServiceError("Token class not implemented: %ld", tkClass);
109 rc = VERR_NOT_IMPLEMENTED;
110 break;
111 }
112
113 if (pvTokenInfo)
114 {
115 DWORD dwRetLength;
116 if (GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
117 {
118 switch (tkClass)
119 {
120 case TokenStatistics:
121 {
122 TOKEN_STATISTICS *pStats = (TOKEN_STATISTICS*)pvTokenInfo;
123 pProc->luid = pStats->AuthenticationId;
124 /** @todo Add more information of TOKEN_STATISTICS as needed. */
125 break;
126 }
127
128 default:
129 /* Should never get here! */
130 break;
131 }
132 rc = VINF_SUCCESS;
133 }
134 else
135 rc = RTErrConvertFromWin32(GetLastError());
136 RTMemFree(pvTokenInfo);
137 }
138 CloseHandle(hToken);
139 }
140 else
141 rc = RTErrConvertFromWin32(GetLastError());
142 CloseHandle(h);
143 return rc;
144}
145
146
147/**
148 * Enumerate all the processes in the system and get the logon user IDs for
149 * them.
150 *
151 * @returns VBox status code.
152 * @param ppaProcs Where to return the process snapshot. This must be
153 * freed by calling VBoxServiceVMInfoWinProcessesFree.
154 *
155 * @param pcProcs Where to store the returned process count.
156 */
157int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs)
158{
159 AssertPtr(ppaProcs);
160 AssertPtr(pcProcs);
161
162 /*
163 * Call EnumProcesses with an increasingly larger buffer until it all fits
164 * or we think something is screwed up.
165 */
166 DWORD cProcesses = 64;
167 PDWORD paPids = NULL;
168 int rc = VINF_SUCCESS;
169 do
170 {
171 /* Allocate / grow the buffer first. */
172 cProcesses *= 2;
173 void *pvNew = RTMemRealloc(paPids, cProcesses * sizeof(DWORD));
174 if (!pvNew)
175 {
176 rc = VERR_NO_MEMORY;
177 break;
178 }
179 paPids = (PDWORD)pvNew;
180
181 /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */
182 DWORD cbRet;
183 if (!EnumProcesses(paPids, cProcesses * sizeof(DWORD), &cbRet))
184 {
185 rc = RTErrConvertFromWin32(GetLastError());
186 break;
187 }
188 if (cbRet < cProcesses * sizeof(DWORD))
189 {
190 cProcesses = cbRet / sizeof(DWORD);
191 break;
192 }
193 } while (cProcesses <= 32768); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
194 if (RT_SUCCESS(rc))
195 {
196 /*
197 * Allocate out process structures and fill data into them.
198 * We currently only try lookup their LUID's.
199 */
200 PVBOXSERVICEVMINFOPROC paProcs;
201 paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC));
202 if (paProcs)
203 {
204 for (DWORD i = 0; i < cProcesses; i++)
205 {
206 paProcs[i].id = paPids[i];
207 rc = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
208 if (RT_FAILURE(rc))
209 {
210 /* Because some processes cannot be opened/parsed on
211 Windows, we should not consider to be this an error here. */
212 rc = VINF_SUCCESS;
213 }
214 }
215
216 /* Save number of processes */
217 if (RT_SUCCESS(rc))
218 {
219 *pcProcs = cProcesses;
220 *ppaProcs = paProcs;
221 }
222 else
223 RTMemFree(paProcs);
224 }
225 else
226 rc = VERR_NO_MEMORY;
227 }
228
229 RTMemFree(paPids);
230 return rc;
231}
232
233/**
234 * Frees the process structures returned by
235 * VBoxServiceVMInfoWinProcessesEnumerate() before.
236 *
237 * @param paProcs What
238 */
239void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs)
240{
241 RTMemFree(paProcs);
242}
243
244/**
245 * Determins whether the specified session has processes on the system.
246 *
247 * @returns true if it has, false if it doesn't.
248 * @param pSession The session.
249 * @param paProcs The process snapshot.
250 * @param cProcs The number of processes in the snaphot.
251 */
252bool VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, VBOXSERVICEVMINFOPROC const *paProcs, DWORD cProcs)
253{
254 AssertPtr(pSession);
255
256 if (!cProcs) /* To be on the safe side. */
257 return false;
258 AssertPtr(paProcs);
259
260 PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
261 NTSTATUS rcNt = LsaGetLogonSessionData(pSession, &pSessionData);
262 if (rcNt != STATUS_SUCCESS)
263 {
264 VBoxServiceError("Could not get logon session data! rcNt=%#x", rcNt);
265 return false;
266 }
267 AssertPtrReturn(pSessionData, false);
268
269 /*
270 * Even if a user seems to be logged in, it could be a stale/orphaned logon
271 * session. So check if we have some processes bound to it by comparing the
272 * session <-> process LUIDs.
273 */
274 for (DWORD i = 0; i < cProcs; i++)
275 {
276 /*VBoxServiceVerbose(3, "%ld:%ld <-> %ld:%ld\n",
277 paProcs[i].luid.HighPart, paProcs[i].luid.LowPart,
278 pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart);*/
279 if ( paProcs[i].luid.HighPart == pSessionData->LogonId.HighPart
280 && paProcs[i].luid.LowPart == pSessionData->LogonId.LowPart)
281 {
282 VBoxServiceVerbose(3, "Users: Session %ld:%ld has active processes\n",
283 pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart);
284 LsaFreeReturnBuffer(pSessionData);
285 return true;
286 }
287 }
288 LsaFreeReturnBuffer(pSessionData);
289 return false;
290}
291
292
293/**
294 * Save and noisy string copy.
295 *
296 * @param pwszDst Destination buffer.
297 * @param cbDst Size in bytes - not WCHAR count!
298 * @param pSrc Source string.
299 * @param pszWhat What this is. For the log.
300 */
301static void VBoxServiceVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat)
302{
303 Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst);
304
305 size_t cbCopy = pSrc->Length;
306 if (cbCopy + sizeof(WCHAR) > cbDst)
307 {
308 VBoxServiceVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n",
309 pszWhat, cbCopy, cbDst);
310 cbCopy = cbDst - sizeof(WCHAR);
311 }
312 if (cbCopy)
313 memcpy(pwszDst, pSrc->Buffer, cbCopy);
314 pwszDst[cbCopy / sizeof(WCHAR)] = '\0';
315}
316
317
318/**
319 * Detects whether a user is logged on.
320 *
321 * @returns true if logged in, false if not (or error).
322 * @param a_pUserInfo Where to return the user information.
323 * @param a_pSession The session to check.
324 */
325bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession)
326{
327 AssertPtr(a_pUserInfo);
328 if (!a_pSession)
329 return false;
330
331 PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
332 NTSTATUS rcNt = LsaGetLogonSessionData(a_pSession, &pSessionData);
333 if (rcNt != STATUS_SUCCESS)
334 {
335 VBoxServiceError("VMInfo/Users: LsaGetLogonSessionData failed, LSA error %#x\n", LsaNtStatusToWinError(rcNt));
336 if (pSessionData)
337 LsaFreeReturnBuffer(pSessionData);
338 return false;
339 }
340 if (!pSessionData)
341 {
342 VBoxServiceError("VMInfo/Users: Invalid logon session data!\n");
343 return false;
344 }
345
346 /*
347 * Only handle users which can login interactively or logged in
348 * remotely over native RDP.
349 */
350 bool fFoundUser = false;
351 DWORD dwErr = NO_ERROR;
352 if ( IsValidSid(pSessionData->Sid)
353 && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
354 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive
355 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive
356 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedRemoteInteractive))
357 {
358 VBoxServiceVerbose(3, "VMInfo/Users: Session data: Name=%ls, Len=%d, SID=%s, LogonID=%ld,%ld\n",
359 pSessionData->UserName.Buffer,
360 pSessionData->UserName.Length,
361 pSessionData->Sid != NULL ? "1" : "0",
362 pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart);
363
364 /*
365 * Copy out relevant data.
366 */
367 VBoxServiceVMInfoWinSafeCopy(a_pUserInfo->wszUser, sizeof(a_pUserInfo->wszUser),
368 &pSessionData->UserName, "User name");
369 VBoxServiceVMInfoWinSafeCopy(a_pUserInfo->wszAuthenticationPackage, sizeof(a_pUserInfo->wszAuthenticationPackage),
370 &pSessionData->AuthenticationPackage, "Authentication pkg name");
371 VBoxServiceVMInfoWinSafeCopy(a_pUserInfo->wszLogonDomain, sizeof(a_pUserInfo->wszLogonDomain),
372 &pSessionData->LogonDomain, "Logon domain name");
373
374 TCHAR szOwnerName[_MAX_PATH] = { 0 };
375 DWORD dwOwnerNameSize = sizeof(szOwnerName);
376 TCHAR szDomainName[_MAX_PATH] = { 0 };
377 DWORD dwDomainNameSize = sizeof(szDomainName);
378 SID_NAME_USE enmOwnerType = SidTypeInvalid;
379 if (!LookupAccountSid(NULL,
380 pSessionData->Sid,
381 szOwnerName,
382 &dwOwnerNameSize,
383 szDomainName,
384 &dwDomainNameSize,
385 &enmOwnerType))
386 {
387 VBoxServiceError("VMInfo/Users: Failed looking up account info for user '%ls': %ld!\n",
388 a_pUserInfo->wszUser, GetLastError());
389 }
390 else
391 {
392 if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */
393 {
394 VBoxServiceVerbose(3, "VMInfo/Users: Account User=%ls, Session=%ld, LUID=%ld,%ld, AuthPkg=%ls, Domain=%ls\n",
395 a_pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart,
396 pSessionData->LogonId.LowPart, a_pUserInfo->wszAuthenticationPackage,
397 a_pUserInfo->wszLogonDomain);
398
399 /* Detect RDP sessions as well. */
400 LPTSTR pBuffer = NULL;
401 DWORD cbRet = 0;
402 int iState = 0;
403 if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
404 pSessionData->Session,
405 WTSConnectState,
406 &pBuffer,
407 &cbRet))
408 {
409 if (cbRet)
410 iState = *pBuffer;
411 VBoxServiceVerbose(3, "VMInfo/Users: Account User=%ls, WTSConnectState=%d\n",
412 a_pUserInfo->wszUser, iState);
413 if ( iState == WTSActive /* User logged on to WinStation. */
414 || iState == WTSShadow /* Shadowing another WinStation. */
415 || iState == WTSDisconnected) /* WinStation logged on without client. */
416 {
417 /** @todo On Vista and W2K, always "old" user name are still
418 * there. Filter out the old one! */
419 VBoxServiceVerbose(3, "VMInfo/Users: Account User=%ls is logged in via TCS/RDP. State=%d\n",
420 a_pUserInfo->wszUser, iState);
421 fFoundUser = true;
422 }
423 if (pBuffer)
424 WTSFreeMemory(pBuffer);
425 }
426 else
427 {
428 VBoxServiceVerbose(3, "VMInfo/Users: Account User=%ls, WTSConnectState returnd %ld\n",
429 a_pUserInfo->wszUser, GetLastError());
430
431 /*
432 * Terminal services don't run (for example in W2K,
433 * nothing to worry about ...). ... or is on the Vista
434 * fast user switching page!
435 */
436 fFoundUser = true;
437 }
438 }
439 }
440 }
441
442 LsaFreeReturnBuffer(pSessionData);
443 return fFoundUser;
444}
445
446
447/**
448 * Retrieves the currently logged in users and stores their names along with the
449 * user count.
450 *
451 * @returns VBox status code.
452 * @param ppszUserList Where to store the user list (separated by commas).
453 * Must be freed with RTStrFree().
454 * @param pcUsersInList Where to store the number of users in the list.
455 */
456int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList)
457{
458 PLUID paSessions = NULL;
459 ULONG cSession = 0;
460
461 /* This function can report stale or orphaned interactive logon sessions
462 of already logged off users (especially in Windows 2000). */
463 NTSTATUS rcNt = LsaEnumerateLogonSessions(&cSession, &paSessions);
464 VBoxServiceVerbose(3, "VMInfo/Users: Found %ld users\n", cSession);
465 if (rcNt != STATUS_SUCCESS)
466 {
467 VBoxServiceError("VMInfo/Users: LsaEnumerate failed with %lu\n", LsaNtStatusToWinError(rcNt));
468 return RTErrConvertFromWin32(LsaNtStatusToWinError(rcNt));
469 }
470
471 PVBOXSERVICEVMINFOPROC paProcs;
472 DWORD cProcs;
473 int rc = VBoxServiceVMInfoWinProcessesEnumerate(&paProcs, &cProcs);
474 if (RT_SUCCESS(rc))
475 {
476 *pcUsersInList = 0;
477 for (ULONG i = 0; i < cSession; i++)
478 {
479 VBOXSERVICEVMINFOUSER UserInfo;
480 if ( VBoxServiceVMInfoWinIsLoggedIn(&UserInfo, &paSessions[i])
481 && VBoxServiceVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs))
482 {
483 if (*pcUsersInList > 0)
484 {
485 rc = RTStrAAppend(ppszUserList, ",");
486 AssertRCBreakStmt(rc, RTStrFree(*ppszUserList));
487 }
488
489 *pcUsersInList += 1;
490
491 char *pszTemp;
492 int rc2 = RTUtf16ToUtf8(UserInfo.wszUser, &pszTemp);
493 if (RT_SUCCESS(rc2))
494 {
495 rc = RTStrAAppend(ppszUserList, pszTemp);
496 RTMemFree(pszTemp);
497 }
498 else
499 rc = RTStrAAppend(ppszUserList, "<string-convertion-error>");
500 AssertRCBreakStmt(rc, RTStrFree(*ppszUserList));
501 }
502 }
503 VBoxServiceVMInfoWinProcessesFree(paProcs);
504 }
505 LsaFreeReturnBuffer(paSessions);
506 return rc;
507}
508
509#endif /* TARGET_NT4 */
510
511int VBoxServiceWinGetComponentVersions(uint32_t uClientID)
512{
513 int rc;
514 char szSysDir[_MAX_PATH] = {0};
515 char szWinDir[_MAX_PATH] = {0};
516 char szDriversDir[_MAX_PATH + 32] = {0};
517
518 /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
519 GetSystemDirectory(szSysDir, _MAX_PATH);
520 GetWindowsDirectory(szWinDir, _MAX_PATH);
521 RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir);
522#ifdef RT_ARCH_AMD64
523 char szSysWowDir[_MAX_PATH + 32] = {0};
524 RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir);
525#endif
526
527 /* The file information table. */
528#ifndef TARGET_NT4
529 const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
530 {
531 { szSysDir, "VBoxControl.exe" },
532 { szSysDir, "VBoxHook.dll" },
533 { szSysDir, "VBoxDisp.dll" },
534 { szSysDir, "VBoxMRXNP.dll" },
535 { szSysDir, "VBoxService.exe" },
536 { szSysDir, "VBoxTray.exe" },
537 { szSysDir, "VBoxGINA.dll" },
538 { szSysDir, "VBoxCredProv.dll" },
539
540 /* On 64-bit we don't yet have the OpenGL DLLs in native format.
541 So just enumerate the 32-bit files in the SYSWOW directory. */
542# ifdef RT_ARCH_AMD64
543 { szSysWowDir, "VBoxOGLarrayspu.dll" },
544 { szSysWowDir, "VBoxOGLcrutil.dll" },
545 { szSysWowDir, "VBoxOGLerrorspu.dll" },
546 { szSysWowDir, "VBoxOGLpackspu.dll" },
547 { szSysWowDir, "VBoxOGLpassthroughspu.dll" },
548 { szSysWowDir, "VBoxOGLfeedbackspu.dll" },
549 { szSysWowDir, "VBoxOGL.dll" },
550# else /* !RT_ARCH_AMD64 */
551 { szSysDir, "VBoxOGLarrayspu.dll" },
552 { szSysDir, "VBoxOGLcrutil.dll" },
553 { szSysDir, "VBoxOGLerrorspu.dll" },
554 { szSysDir, "VBoxOGLpackspu.dll" },
555 { szSysDir, "VBoxOGLpassthroughspu.dll" },
556 { szSysDir, "VBoxOGLfeedbackspu.dll" },
557 { szSysDir, "VBoxOGL.dll" },
558# endif /* !RT_ARCH_AMD64 */
559
560 { szDriversDir, "VBoxGuest.sys" },
561 { szDriversDir, "VBoxMouse.sys" },
562 { szDriversDir, "VBoxSF.sys" },
563 { szDriversDir, "VBoxVideo.sys" },
564 };
565
566#else /* TARGET_NT4 */
567 const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
568 {
569 { szSysDir, "VBoxControl.exe" },
570 { szSysDir, "VBoxHook.dll" },
571 { szSysDir, "VBoxDisp.dll" },
572 { szSysDir, "VBoxServiceNT.exe" },
573 { szSysDir, "VBoxTray.exe" },
574
575 { szDriversDir, "VBoxGuestNT.sys" },
576 { szDriversDir, "VBoxMouseNT.sys" },
577 { szDriversDir, "VBoxVideo.sys" },
578 };
579#endif /* TARGET_NT4 */
580
581 for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
582 {
583 char szVer[128];
584 VBoxServiceGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
585 char szPropPath[256];
586 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
587 rc = VBoxServiceWritePropF(uClientID, szPropPath, "%s", szVer);
588 }
589
590 return VINF_SUCCESS;
591}
592
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