- 時間撮記:
- 2011-4-5 上午09:27:33 (14 年 以前)
- 位置:
- trunk/src/VBox/Additions/common/VBoxService
- 檔案:
-
- 新增 2 筆資料
- 修改 4 筆資料
圖例:
- 未更動
- 新增
- 刪除
-
trunk/src/VBox/Additions/common/VBoxService/Makefile.kmk
r36305 r36548 5 5 6 6 # 7 # Copyright (C) 2007-201 0Oracle Corporation7 # Copyright (C) 2007-2011 Oracle Corporation 8 8 # 9 9 # This file is part of VirtualBox Open Source Edition (OSE), as … … 70 70 VBoxServiceControl.cpp \ 71 71 VBoxServiceControlExec.cpp \ 72 VBoxServiceControlExecThread.cpp \ 72 73 VBoxServicePipeBuf.cpp 73 74 endif -
trunk/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp
r35060 r36548 5 5 6 6 /* 7 * Copyright (C) 201 0Oracle Corporation7 * Copyright (C) 2011 Oracle Corporation 8 8 * 9 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 20 20 * Header Files * 21 21 *******************************************************************************/ 22 #include <iprt/asm.h>23 22 #include <iprt/assert.h> 24 23 #include <iprt/getopt.h> … … 30 29 #include "VBoxServiceInternal.h" 31 30 #include "VBoxServiceUtils.h" 31 #include "VBoxServiceControlExecThread.h" 32 32 33 33 using namespace guestControl; … … 42 42 /** The Guest Control service client ID. */ 43 43 static uint32_t g_GuestControlSvcClientID = 0; 44 /** List of spawned processes */44 /** List of spawned processes. */ 45 45 RTLISTNODE g_GuestControlExecThreads; 46 46 /** Critical section protecting g_GuestControlExecThreads. */ 47 RTCRITSECT g_GuestControlExecThreadsCritSect; 47 48 48 49 /** @copydoc VBOXSERVICE::pfnPreInit */ … … 86 87 /* Init thread list. */ 87 88 RTListInit(&g_GuestControlExecThreads); 89 rc = RTCritSectInit(&g_GuestControlExecThreadsCritSect); 90 AssertRC(rc); 88 91 } 89 92 else … … 216 219 VBoxServiceVerbose(3, "Control: Terminating ...\n"); 217 220 218 /* Signal all threads that we want to shutdown. */ 219 PVBOXSERVICECTRLTHREAD pNode; 220 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node) 221 ASMAtomicXchgBool(&pNode->fShutdown, true); 222 223 /* Wait for threads to shutdown. */ 224 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node) 225 { 226 if (pNode->Thread != NIL_RTTHREAD) 227 { 228 /* Wait a bit ... */ 229 int rc2 = RTThreadWait(pNode->Thread, 30 * 1000 /* Wait 30 seconds max. */, NULL); 230 if (RT_FAILURE(rc2)) 231 VBoxServiceError("Control: Thread failed to stop; rc2=%Rrc\n", rc2); 232 } 233 234 /* Destroy thread specific data. */ 235 switch (pNode->enmType) 236 { 237 case kVBoxServiceCtrlThreadDataExec: 238 VBoxServiceControlExecDestroyThreadData((PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData); 239 break; 240 241 default: 242 break; 243 } 244 } 245 246 /* Finally destroy thread list. */ 247 pNode = RTListGetFirst(&g_GuestControlExecThreads, VBOXSERVICECTRLTHREAD, Node); 248 while (pNode) 249 { 250 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pNode->Node, VBOXSERVICECTRLTHREAD, Node); 251 bool fLast = RTListNodeIsLast(&g_GuestControlExecThreads, &pNode->Node); 252 253 RTListNodeRemove(&pNode->Node); 254 RTMemFree(pNode); 255 256 if (fLast) 257 break; 258 259 pNode = pNext; 260 } 221 VBoxServiceControlExecThreadsShutdown(); 261 222 262 223 VbglR3GuestCtrlDisconnect(g_GuestControlSvcClientID); -
trunk/src/VBox/Additions/common/VBoxService/VBoxServiceControlExec.cpp
r36305 r36548 43 43 #include "VBoxServiceUtils.h" 44 44 #include "VBoxServicePipeBuf.h" 45 #include "VBoxServiceControlExecThread.h" 45 46 46 47 using namespace guestControl; 47 48 48 49 extern RTLISTNODE g_GuestControlExecThreads; 50 extern RTCRITSECT g_GuestControlExecThreadsCritSect; 49 51 50 52 … … 80 82 /* Remove stdin error handle from set. */ 81 83 rc = RTPollSetRemove(hPollSet, VBOXSERVICECTRLPIPEID_STDIN_ERROR); 82 AssertRC(rc); 84 /* Don't assert if writable handle is not in poll set anymore. */ 85 if ( RT_FAILURE(rc) 86 && rc != VERR_POLL_HANDLE_ID_NOT_FOUND) 87 { 88 AssertRC(rc); 89 } 90 else 91 rc = VINF_SUCCESS; 83 92 84 93 return rc; … … 182 191 183 192 /** 184 * Handle a transport event or successful pfnPollIn() call.185 *186 * @returns IPRT status code from client send.187 * @retval VINF_EOF indicates ABORT command.188 *189 * @param hPollSet The polling set.190 * @param fPollEvt The event mask returned by RTPollNoResume.191 * @param idPollHnd The handle ID.192 * @param hStdInW The standard input pipe.193 * @param pStdInBuf The standard input buffer.194 */195 static int VBoxServiceControlExecProcHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd,196 PRTPIPE phStdInW, PVBOXSERVICECTRLEXECPIPEBUF pStdInBuf)197 {198 return 0; //RTPollSetAddPipe(hPollSet, *phStdInW, RTPOLL_EVT_WRITE, 4 /*TXSEXECHNDID_STDIN_WRITABLE*/);199 }200 201 202 /**203 193 * Handle pending output data or error on standard out, standard error or the 204 194 * test pipe. … … 290 280 291 281 /** 292 * Execution loop which (usually)runs in a dedicated per-started-process thread and282 * Execution loop which runs in a dedicated per-started-process thread and 293 283 * handles all pipe input/output and signalling stuff. 294 284 * … … 637 627 638 628 return rc; 639 }640 641 642 /**643 * Allocates and gives back a thread data struct which then can be used by the worker thread.644 * Needs to be freed with VBoxServiceControlExecDestroyThreadData().645 *646 * @return IPRT status code.647 * @param pThread The thread's handle to allocate the data for.648 * @param u32ContextID The context ID bound to this request / command.649 * @param pszCmd Full qualified path of process to start (without arguments).650 * @param uFlags Process execution flags.651 * @param pszArgs String of arguments to pass to the process to start.652 * @param uNumArgs Number of arguments specified in pszArgs.653 * @param pszEnv String of environment variables ("FOO=BAR") to pass to the process654 * to start.655 * @param cbEnv Size (in bytes) of environment variables.656 * @param uNumEnvVars Number of environment variables specified in pszEnv.657 * @param pszUser User name (account) to start the process under.658 * @param pszPassword Password of specified user name (account).659 * @param uTimeLimitMS Time limit (in ms) of the process' life time.660 */661 static int VBoxServiceControlExecAllocateThreadData(PVBOXSERVICECTRLTHREAD pThread,662 uint32_t u32ContextID,663 const char *pszCmd, uint32_t uFlags,664 const char *pszArgs, uint32_t uNumArgs,665 const char *pszEnv, uint32_t cbEnv, uint32_t uNumEnvVars,666 const char *pszUser, const char *pszPassword, uint32_t uTimeLimitMS)667 {668 AssertPtr(pThread);669 670 /* General stuff. */671 pThread->Node.pPrev = NULL;672 pThread->Node.pNext = NULL;673 674 pThread->fShutdown = false;675 pThread->fStarted = false;676 pThread->fStopped = false;677 678 pThread->uContextID = u32ContextID;679 /* ClientID will be assigned when thread is started! */680 681 /* Specific stuff. */682 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREADDATAEXEC));683 if (pData == NULL)684 return VERR_NO_MEMORY;685 686 pData->uPID = 0; /* Don't have a PID yet. */687 pData->pszCmd = RTStrDup(pszCmd);688 pData->uFlags = uFlags;689 pData->uNumEnvVars = 0;690 pData->uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */691 692 /* Prepare argument list. */693 int rc = RTGetOptArgvFromString(&pData->papszArgs, (int*)&pData->uNumArgs,694 (uNumArgs > 0) ? pszArgs : "", NULL);695 /* Did we get the same result? */696 Assert(uNumArgs == pData->uNumArgs);697 698 if (RT_SUCCESS(rc))699 {700 /* Prepare environment list. */701 if (uNumEnvVars)702 {703 pData->papszEnv = (char **)RTMemAlloc(uNumEnvVars * sizeof(char*));704 AssertPtr(pData->papszEnv);705 pData->uNumEnvVars = uNumEnvVars;706 707 const char *pszCur = pszEnv;708 uint32_t i = 0;709 uint32_t cbLen = 0;710 while (cbLen < cbEnv)711 {712 /* sanity check */713 if (i >= uNumEnvVars)714 {715 rc = VERR_INVALID_PARAMETER;716 break;717 }718 int cbStr = RTStrAPrintf(&pData->papszEnv[i++], "%s", pszCur);719 if (cbStr < 0)720 {721 rc = VERR_NO_STR_MEMORY;722 break;723 }724 pszCur += cbStr + 1; /* Skip terminating '\0' */725 cbLen += cbStr + 1; /* Skip terminating '\0' */726 }727 }728 729 pData->pszUser = RTStrDup(pszUser);730 pData->pszPassword = RTStrDup(pszPassword);731 pData->uTimeLimitMS = uTimeLimitMS;732 733 /* Adjust time limit value. */734 pData->uTimeLimitMS = ( uTimeLimitMS == UINT32_MAX735 || uTimeLimitMS == 0)736 ? RT_INDEFINITE_WAIT : uTimeLimitMS;737 738 /* Init buffers. */739 rc = VBoxServicePipeBufInit(&pData->stdOut, false /*fNeedNotificationPipe*/);740 if (RT_SUCCESS(rc))741 {742 rc = VBoxServicePipeBufInit(&pData->stdErr, false /*fNeedNotificationPipe*/);743 if (RT_SUCCESS(rc))744 rc = VBoxServicePipeBufInit(&pData->stdIn, true /*fNeedNotificationPipe*/);745 }746 }747 748 if (RT_FAILURE(rc))749 {750 VBoxServiceControlExecDestroyThreadData(pData);751 }752 else753 {754 pThread->enmType = kVBoxServiceCtrlThreadDataExec;755 pThread->pvData = pData;756 }757 return rc;758 }759 760 761 /**762 * Frees an allocated thread data structure along with all its allocated parameters.763 *764 * @param pData Pointer to thread data to free.765 */766 void VBoxServiceControlExecDestroyThreadData(PVBOXSERVICECTRLTHREADDATAEXEC pData)767 {768 if (pData)769 {770 RTStrFree(pData->pszCmd);771 if (pData->uNumEnvVars)772 {773 for (uint32_t i = 0; i < pData->uNumEnvVars; i++)774 RTStrFree(pData->papszEnv[i]);775 RTMemFree(pData->papszEnv);776 }777 RTGetOptArgvFree(pData->papszArgs);778 RTStrFree(pData->pszUser);779 RTStrFree(pData->pszPassword);780 781 VBoxServicePipeBufDestroy(&pData->stdOut);782 VBoxServicePipeBufDestroy(&pData->stdErr);783 VBoxServicePipeBufDestroy(&pData->stdIn);784 785 RTMemFree(pData);786 pData = NULL;787 }788 629 } 789 630 … … 1218 1059 1219 1060 /** 1220 * Finds a (formerly) started process given by its PID.1221 *1222 * @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL.1223 * @param uPID PID to search for.1224 */1225 static PVBOXSERVICECTRLTHREAD VBoxServiceControlExecFindProcess(uint32_t uPID)1226 {1227 PVBOXSERVICECTRLTHREAD pNode;1228 bool fFound = false;1229 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)1230 {1231 if ( pNode->fStarted1232 && pNode->enmType == kVBoxServiceCtrlThreadDataExec)1233 {1234 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;1235 if (pData && pData->uPID == uPID)1236 {1237 return pNode;1238 }1239 }1240 }1241 return NULL;1242 }1243 1244 1245 /**1246 1061 * Thread main routine for a started process. 1247 1062 * … … 1282 1097 const char *pszUser, const char *pszPassword, uint32_t uTimeLimitMS) 1283 1098 { 1099 int rc; 1100 1284 1101 PVBOXSERVICECTRLTHREAD pThread = (PVBOXSERVICECTRLTHREAD)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREAD)); 1285 1286 int rc;1287 1102 if (pThread) 1288 1103 { 1289 rc = VBoxServiceControlExec AllocateThreadData(pThread,1290 1291 1292 1293 1294 1295 1104 rc = VBoxServiceControlExecThreadAlloc(pThread, 1105 uContextID, 1106 pszCmd, uFlags, 1107 pszArgs, uNumArgs, 1108 pszEnv, cbEnv, uNumEnvVars, 1109 pszUser, pszPassword, 1110 uTimeLimitMS); 1296 1111 if (RT_SUCCESS(rc)) 1297 1112 { … … 1323 1138 1324 1139 if (RT_FAILURE(rc)) 1325 VBoxServiceControlExec DestroyThreadData((PVBOXSERVICECTRLTHREADDATAEXEC)pThread->pvData);1140 VBoxServiceControlExecThreadDestroy((PVBOXSERVICECTRLTHREADDATAEXEC)pThread->pvData); 1326 1141 } 1327 1142 if (RT_FAILURE(rc)) … … 1419 1234 AssertPtrReturn(pabBuffer, VERR_NO_MEMORY); 1420 1235 1236 uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */ 1237 uint32_t cbWritten = 0; /* Number of bytes written to the guest. */ 1238 1421 1239 /* 1422 1240 * Ask the host for the input data. … … 1438 1256 { 1439 1257 /* 1440 * Resolve the PID.1258 * Is this the last input block we need to deliver? Then let the pipe know ... 1441 1259 */ 1442 VBoxServiceVerbose(4, "ControlExec: Input (PID %u) received: cbSize=%u\n", uPID, cbSize); 1443 1444 PVBOXSERVICECTRLTHREAD pNode = VBoxServiceControlExecFindProcess(uPID); 1445 if (pNode) 1446 { 1447 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData; 1448 AssertPtr(pData); 1449 1450 /* 1451 * Is this the last input block we need to deliver? Then let the pipe know ... 1452 */ 1453 bool fPendingClose = false; 1454 if (uFlags & INPUT_FLAG_EOF) 1455 { 1456 fPendingClose = true; 1457 VBoxServiceVerbose(4, "ControlExec: Got last input block (PID %u) ...\n", uPID); 1458 } 1459 1460 uint32_t cbWritten; 1461 if (VBoxServicePipeBufIsEnabled(&pData->stdIn)) 1462 { 1463 /* 1464 * Feed the data to the pipe. 1465 */ 1466 rc = VBoxServicePipeBufWriteToBuf(&pData->stdIn, pabBuffer, 1467 cbSize, fPendingClose, &cbWritten); 1468 #ifdef DEBUG 1469 VBoxServiceVerbose(4, "ControlExec: Written to StdIn buffer (PID %u): rc=%Rrc, uFlags=0x%x, cbAlloc=%u, cbSize=%u, cbOffset=%u\n", 1470 uPID, rc, uFlags, 1471 pData->stdIn.cbAllocated, pData->stdIn.cbSize, pData->stdIn.cbOffset); 1472 #endif 1473 } 1474 else 1475 { 1476 /* If input buffer is not enabled anymore we cannot handle that data ... */ 1477 rc = VERR_BAD_PIPE; 1478 } 1479 1480 uint32_t uStatus = INPUT_STS_UNDEFINED; 1481 if (RT_SUCCESS(rc)) 1482 { 1483 if (cbWritten || !cbSize) /* Did we write something or was there anything to write at all? */ 1484 { 1485 uStatus = INPUT_STS_WRITTEN; 1486 uFlags = 0; 1487 } 1488 } 1489 else 1490 { 1491 if (rc == VERR_BAD_PIPE) 1492 uStatus = INPUT_STS_TERMINATED; 1493 else if (rc == VERR_BUFFER_OVERFLOW) 1494 uStatus = INPUT_STS_OVERFLOW; 1495 else 1496 { 1497 uStatus = INPUT_STS_ERROR; 1498 uFlags = rc; 1499 } 1500 } 1501 1502 if (uStatus > INPUT_STS_UNDEFINED) 1503 { 1504 VBoxServiceVerbose(3, "ControlExec: VbglR3GuestCtrlExecReportStatusIn (PID %u), Status = %u, Flags=%u, cbWritten=%u\n", 1505 uPID, uStatus, uFlags, cbWritten); 1506 1507 /* Note: Since the context ID is unique the request *has* to be completed here, 1508 * regardless whether we got data or not! Otherwise the progress object 1509 * on the host never will get completed! */ 1510 rc = VbglR3GuestCtrlExecReportStatusIn(u32ClientId, uContextID, uPID, 1511 uStatus, uFlags, (uint32_t)cbWritten); 1260 bool fPendingClose = false; 1261 if (uFlags & INPUT_FLAG_EOF) 1262 { 1263 fPendingClose = true; 1264 VBoxServiceVerbose(4, "ControlExec: Got last input block (PID %u) of size %u ...\n", uPID, cbSize); 1265 } 1266 1267 rc = VBoxServiceControlExecThreadSetInput(uPID, fPendingClose, pabBuffer, 1268 cbSize, &cbWritten); 1269 VBoxServiceVerbose(4, "ControlExec: Written input (PID %u): rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n", 1270 uPID, rc, uFlags, fPendingClose, cbSize, cbWritten); 1271 if (RT_SUCCESS(rc)) 1272 { 1273 if (cbWritten || !cbSize) /* Did we write something or was there anything to write at all? */ 1274 { 1275 uStatus = INPUT_STS_WRITTEN; 1276 uFlags = 0; 1512 1277 } 1513 1278 } 1514 1279 else 1515 rc = VERR_NOT_FOUND; /* PID not found! */ 1280 { 1281 if (rc == VERR_BAD_PIPE) 1282 uStatus = INPUT_STS_TERMINATED; 1283 else if (rc == VERR_BUFFER_OVERFLOW) 1284 uStatus = INPUT_STS_OVERFLOW; 1285 } 1516 1286 } 1517 1287 RTMemFree(pabBuffer); 1288 1289 /* 1290 * If there was an error and we did not set the host status 1291 * yet, then do it now. 1292 */ 1293 if ( RT_FAILURE(rc) 1294 && uStatus == INPUT_STS_UNDEFINED) 1295 { 1296 uStatus = INPUT_STS_ERROR; 1297 uFlags = rc; 1298 } 1299 Assert(uStatus > INPUT_STS_UNDEFINED); 1300 1301 VBoxServiceVerbose(3, "ControlExec: Input processed (PID %u), Status=%u, Flags=0x%x, cbWritten=%u\n", 1302 uPID, uStatus, uFlags, cbWritten); 1303 1304 /* Note: Since the context ID is unique the request *has* to be completed here, 1305 * regardless whether we got data or not! Otherwise the progress object 1306 * on the host never will get completed! */ 1307 rc = VbglR3GuestCtrlExecReportStatusIn(u32ClientId, uContextID, uPID, 1308 uStatus, uFlags, (uint32_t)cbWritten); 1309 1518 1310 VBoxServiceVerbose(3, "ControlExec: VBoxServiceControlExecHandleCmdSetInput returned with %Rrc\n", rc); 1519 1311 return rc; … … 1540 1332 if (RT_SUCCESS(rc)) 1541 1333 { 1542 PVBOXSERVICECTRLTHREAD pNode = VBoxServiceControlExecFindProcess(uPID); 1543 if (pNode) 1544 { 1545 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData; 1546 AssertPtr(pData); 1547 1548 const uint32_t cbSize = _1M; 1549 uint32_t cbRead = cbSize; 1550 uint8_t *pBuf = (uint8_t*)RTMemAlloc(cbSize); 1551 if (pBuf) 1552 { 1553 /* If the stdout pipe buffer is enabled (that is, still could be filled by a running 1554 * process) wait for the signal to arrive so that we don't return without any actual 1555 * data read. */ 1556 if (VBoxServicePipeBufIsEnabled(&pData->stdOut)) 1557 { 1558 VBoxServiceVerbose(4, "ControlExec: Waiting for output data becoming ready ...\n"); 1559 /** @todo Add a timeout here! */ 1560 rc = VBoxServicePipeBufWaitForEvent(&pData->stdOut, RT_INDEFINITE_WAIT); 1561 } 1562 if (RT_SUCCESS(rc)) 1563 { 1564 /** @todo Use uHandleID to distinguish between stdout/stderr! */ 1565 rc = VBoxServicePipeBufRead(&pData->stdOut, pBuf, cbSize, &cbRead); 1566 if (RT_SUCCESS(rc)) 1567 { 1568 /* Note: Since the context ID is unique the request *has* to be completed here, 1569 * regardless whether we got data or not! Otherwise the progress object 1570 * on the host never will get completed! */ 1571 /* cbRead now contains actual size. */ 1572 rc = VbglR3GuestCtrlExecSendOut(u32ClientId, uContextID, uPID, 0 /* Handle ID */, 0 /* Flags */, 1573 pBuf, cbRead); 1574 } 1575 } 1576 RTMemFree(pBuf); 1577 } 1578 else 1579 rc = VERR_NO_MEMORY; 1334 uint32_t cbRead; 1335 uint8_t *pBuf = (uint8_t*)RTMemAlloc(_64K); 1336 if (pBuf) 1337 { 1338 rc = VBoxServiceControlExecThreadGetOutput(uPID, uHandleID, RT_INDEFINITE_WAIT /* Timeout */, 1339 pBuf, _64K /* cbSize */, &cbRead); 1340 if (RT_SUCCESS(rc)) 1341 { 1342 /* Note: Since the context ID is unique the request *has* to be completed here, 1343 * regardless whether we got data or not! Otherwise the progress object 1344 * on the host never will get completed! */ 1345 /* cbRead now contains actual size. */ 1346 rc = VbglR3GuestCtrlExecSendOut(u32ClientId, uContextID, uPID, uHandleID, 0 /* Flags */, 1347 pBuf, cbRead); 1348 } 1349 RTMemFree(pBuf); 1580 1350 } 1581 1351 else 1582 rc = VERR_NO T_FOUND; /* PID not found! */1352 rc = VERR_NO_MEMORY; 1583 1353 } 1584 1354 else -
trunk/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h
r36338 r36548 307 307 const char *pszEnv, uint32_t cbEnv, uint32_t uNumEnvVars, 308 308 const char *pszUser, const char *pszPassword, uint32_t uTimeLimitMS); 309 extern void VBoxServiceControlExec DestroyThreadData(PVBOXSERVICECTRLTHREADDATAEXEC pThread);309 extern void VBoxServiceControlExecThreadDestroy(PVBOXSERVICECTRLTHREADDATAEXEC pThread); 310 310 #endif /* VBOX_WITH_GUEST_CONTROL */ 311 311
注意:
瀏覽 TracChangeset
來幫助您使用更動檢視器