Changeset 42864 in vbox for trunk/src/VBox/Main/src-client
- Timestamp:
- Aug 17, 2012 1:36:01 PM (13 years ago)
- svn:sync-xref-src-repo-rev:
- 80134
- Location:
- trunk/src/VBox/Main/src-client
- Files:
-
- 4 deleted
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/src-client/GuestCtrlImpl.cpp
r42758 r42864 47 47 #ifdef VBOX_WITH_GUEST_CONTROL 48 48 /** 49 * Appends environment variables to the environment block.50 *51 * Each var=value pair is separated by the null character ('\\0'). The whole52 * block will be stored in one blob and disassembled on the guest side later to53 * fit into the HGCM param structure.54 *55 * @returns VBox status code.56 *57 * @param pszEnvVar The environment variable=value to append to the58 * environment block.59 * @param ppvList This is actually a pointer to a char pointer60 * variable which keeps track of the environment block61 * that we're constructing.62 * @param pcbList Pointer to the variable holding the current size of63 * the environment block. (List is a misnomer, go64 * ahead a be confused.)65 * @param pcEnvVars Pointer to the variable holding count of variables66 * stored in the environment block.67 */68 int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars)69 {70 int rc = VINF_SUCCESS;71 uint32_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2);72 if (*ppvList)73 {74 uint32_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */75 char *pvTmp = (char *)RTMemRealloc(*ppvList, cbNewLen);76 if (pvTmp == NULL)77 rc = VERR_NO_MEMORY;78 else79 {80 memcpy(pvTmp + *pcbList, pszEnv, cchEnv);81 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */82 *ppvList = (void **)pvTmp;83 }84 }85 else86 {87 char *pszTmp;88 if (RTStrAPrintf(&pszTmp, "%s", pszEnv) >= 0)89 {90 *ppvList = (void **)pszTmp;91 /* Reset counters. */92 *pcEnvVars = 0;93 *pcbList = 0;94 }95 }96 if (RT_SUCCESS(rc))97 {98 *pcbList += cchEnv + 1; /* Include zero termination. */99 *pcEnvVars += 1; /* Increase env variable count. */100 }101 return rc;102 }103 104 /**105 * Adds a callback with a user provided data block and an optional progress object106 * to the callback map. A callback is identified by a unique context ID which is used107 * to identify a callback from the guest side.108 *109 * @return IPRT status code.110 * @param pCallback111 * @param puContextID112 */113 int Guest::callbackAdd(const PVBOXGUESTCTRL_CALLBACK pCallback, uint32_t *puContextID)114 {115 AssertPtrReturn(pCallback, VERR_INVALID_PARAMETER);116 /* puContextID is optional. */117 118 int rc = VERR_NOT_FOUND;119 120 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);121 122 /* Create a new context ID and assign it. */123 uint32_t uNewContextID = 0;124 uint32_t uTries = 0;125 for (;;)126 {127 /* Create a new context ID ... */128 uNewContextID = ASMAtomicIncU32(&mNextContextID);129 if (uNewContextID == UINT32_MAX)130 ASMAtomicUoWriteU32(&mNextContextID, 1000);131 /* Is the context ID already used? Try next ID ... */132 if (!callbackExists(uNewContextID))133 {134 /* Callback with context ID was not found. This means135 * we can use this context ID for our new callback we want136 * to add below. */137 rc = VINF_SUCCESS;138 break;139 }140 141 if (++uTries == UINT32_MAX)142 break; /* Don't try too hard. */143 }144 145 if (RT_SUCCESS(rc))146 {147 /* Add callback with new context ID to our callback map. */148 mCallbackMap[uNewContextID] = *pCallback;149 Assert(mCallbackMap.size());150 151 /* Report back new context ID. */152 if (puContextID)153 *puContextID = uNewContextID;154 }155 156 return rc;157 }158 159 /**160 * Assigns a host PID to a specified context.161 * Does not do locking!162 *163 * @param uContextID164 * @param uHostPID165 */166 int Guest::callbackAssignHostPID(uint32_t uContextID, uint32_t uHostPID)167 {168 AssertReturn(uContextID, VERR_INVALID_PARAMETER);169 AssertReturn(uHostPID, VERR_INVALID_PARAMETER);170 171 int rc = VINF_SUCCESS;172 173 CallbackMapIter it = mCallbackMap.find(uContextID);174 if (it == mCallbackMap.end())175 return VERR_NOT_FOUND;176 177 it->second.uHostPID = uHostPID;178 179 return VINF_SUCCESS;180 }181 182 /**183 * Destroys the formerly allocated callback data. The callback then184 * needs to get removed from the callback map via callbackRemove().185 * Does not do locking!186 *187 * @param uContextID188 */189 void Guest::callbackDestroy(uint32_t uContextID)190 {191 AssertReturnVoid(uContextID);192 193 CallbackMapIter it = mCallbackMap.find(uContextID);194 if (it != mCallbackMap.end())195 {196 LogFlowFunc(("Callback with CID=%u found\n", uContextID));197 if (it->second.pvData)198 {199 LogFlowFunc(("Destroying callback with CID=%u ...\n", uContextID));200 201 callbackFreeUserData(it->second.pvData);202 it->second.cbData = 0;203 }204 }205 }206 207 /**208 * Removes a callback from the callback map.209 * Does not do locking!210 *211 * @param uContextID212 */213 void Guest::callbackRemove(uint32_t uContextID)214 {215 callbackDestroy(uContextID);216 217 mCallbackMap.erase(uContextID);218 }219 220 /**221 * Checks whether a callback with the given context ID222 * exists or not.223 * Does not do locking!224 *225 * @return bool True, if callback exists, false if not.226 * @param uContextID Context ID to check.227 */228 bool Guest::callbackExists(uint32_t uContextID)229 {230 AssertReturn(uContextID, false);231 232 CallbackMapIter it = mCallbackMap.find(uContextID);233 return (it == mCallbackMap.end()) ? false : true;234 }235 236 /**237 * Frees the user data (actual context data) of a callback.238 * Does not do locking!239 *240 * @param pvData Data to free.241 */242 void Guest::callbackFreeUserData(void *pvData)243 {244 if (pvData)245 {246 RTMemFree(pvData);247 pvData = NULL;248 }249 }250 251 /**252 * Retrieves the (generated) host PID of a given callback.253 *254 * @return The host PID, if found, 0 otherwise.255 * @param uContextID Context ID to lookup host PID for.256 * @param puHostPID Where to store the host PID.257 */258 uint32_t Guest::callbackGetHostPID(uint32_t uContextID)259 {260 AssertReturn(uContextID, 0);261 262 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);263 264 CallbackMapIterConst it = mCallbackMap.find(uContextID);265 if (it == mCallbackMap.end())266 return 0;267 268 return it->second.uHostPID;269 }270 271 int Guest::callbackGetUserData(uint32_t uContextID, eVBoxGuestCtrlCallbackType *pEnmType,272 void **ppvData, size_t *pcbData)273 {274 AssertReturn(uContextID, VERR_INVALID_PARAMETER);275 /* pEnmType is optional. */276 /* ppvData is optional. */277 /* pcbData is optional. */278 279 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);280 281 CallbackMapIterConst it = mCallbackMap.find(uContextID);282 if (it == mCallbackMap.end())283 return VERR_NOT_FOUND;284 285 if (pEnmType)286 *pEnmType = it->second.mType;287 288 if ( ppvData289 && it->second.cbData)290 {291 void *pvData = RTMemAlloc(it->second.cbData);292 AssertPtrReturn(pvData, VERR_NO_MEMORY);293 memcpy(pvData, it->second.pvData, it->second.cbData);294 *ppvData = pvData;295 }296 297 if (pcbData)298 *pcbData = it->second.cbData;299 300 return VINF_SUCCESS;301 }302 303 /* Does not do locking! Caller has to take care of it because the caller needs to304 * modify the data ...*/305 void* Guest::callbackGetUserDataMutableRaw(uint32_t uContextID, size_t *pcbData)306 {307 /* uContextID can be 0. */308 /* pcbData is optional. */309 310 CallbackMapIterConst it = mCallbackMap.find(uContextID);311 if (it != mCallbackMap.end())312 {313 if (pcbData)314 *pcbData = it->second.cbData;315 return it->second.pvData;316 }317 318 return NULL;319 }320 321 int Guest::callbackInit(PVBOXGUESTCTRL_CALLBACK pCallback, eVBoxGuestCtrlCallbackType enmType,322 ComPtr<Progress> pProgress)323 {324 AssertPtrReturn(pCallback, VERR_INVALID_POINTER);325 /* Everything else is optional. */326 327 int vrc = VINF_SUCCESS;328 switch (enmType)329 {330 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_START:331 {332 PCALLBACKDATAEXECSTATUS pData = (PCALLBACKDATAEXECSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECSTATUS));333 AssertPtrReturn(pData, VERR_NO_MEMORY);334 RT_BZERO(pData, sizeof(CALLBACKDATAEXECSTATUS));335 pCallback->cbData = sizeof(CALLBACKDATAEXECSTATUS);336 pCallback->pvData = pData;337 break;338 }339 340 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT:341 {342 PCALLBACKDATAEXECOUT pData = (PCALLBACKDATAEXECOUT)RTMemAlloc(sizeof(CALLBACKDATAEXECOUT));343 AssertPtrReturn(pData, VERR_NO_MEMORY);344 RT_BZERO(pData, sizeof(CALLBACKDATAEXECOUT));345 pCallback->cbData = sizeof(CALLBACKDATAEXECOUT);346 pCallback->pvData = pData;347 break;348 }349 350 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS:351 {352 PCALLBACKDATAEXECINSTATUS pData = (PCALLBACKDATAEXECINSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECINSTATUS));353 AssertPtrReturn(pData, VERR_NO_MEMORY);354 RT_BZERO(pData, sizeof(CALLBACKDATAEXECINSTATUS));355 pCallback->cbData = sizeof(CALLBACKDATAEXECINSTATUS);356 pCallback->pvData = pData;357 break;358 }359 360 default:361 vrc = VERR_INVALID_PARAMETER;362 break;363 }364 365 if (RT_SUCCESS(vrc))366 {367 /* Init/set common stuff. */368 pCallback->mType = enmType;369 pCallback->pProgress = pProgress;370 }371 372 return vrc;373 }374 375 bool Guest::callbackIsCanceled(uint32_t uContextID)376 {377 if (!uContextID)378 return true; /* If no context ID given then take a shortcut. */379 380 ComPtr<IProgress> pProgress;381 {382 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);383 384 CallbackMapIterConst it = mCallbackMap.find(uContextID);385 if (it != mCallbackMap.end())386 pProgress = it->second.pProgress;387 }388 389 if (pProgress)390 {391 BOOL fCanceled = FALSE;392 HRESULT hRC = pProgress->COMGETTER(Canceled)(&fCanceled);393 if ( SUCCEEDED(hRC)394 && !fCanceled)395 {396 return false;397 }398 }399 400 return true; /* No progress / error means canceled. */401 }402 403 bool Guest::callbackIsComplete(uint32_t uContextID)404 {405 AssertReturn(uContextID, true);406 407 ComPtr<IProgress> pProgress;408 {409 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);410 411 CallbackMapIterConst it = mCallbackMap.find(uContextID);412 if (it != mCallbackMap.end())413 pProgress = it->second.pProgress;414 }415 416 if (pProgress)417 {418 BOOL fCompleted = FALSE;419 HRESULT hRC = pProgress->COMGETTER(Completed)(&fCompleted);420 if ( SUCCEEDED(hRC)421 && fCompleted)422 {423 return true;424 }425 }426 427 return false;428 }429 430 int Guest::callbackMoveForward(uint32_t uContextID, const char *pszMessage)431 {432 AssertReturn(uContextID, VERR_INVALID_PARAMETER);433 AssertPtrReturn(pszMessage, VERR_INVALID_PARAMETER);434 435 ComPtr<IProgress> pProgress;436 {437 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);438 439 CallbackMapIterConst it = mCallbackMap.find(uContextID);440 if (it != mCallbackMap.end())441 pProgress = it->second.pProgress;442 }443 444 if (pProgress)445 {446 HRESULT hr = pProgress->SetNextOperation(Bstr(pszMessage).raw(), 1 /* Weight */);447 if (FAILED(hr))448 return VERR_CANCELLED;449 450 return VINF_SUCCESS;451 }452 453 return VERR_NOT_FOUND;454 }455 456 /**457 * Notifies a specified callback about its final status.458 *459 * @return IPRT status code.460 * @param uContextID461 * @param iRC462 * @param pszMessage463 */464 int Guest::callbackNotifyEx(uint32_t uContextID, int iRC, const char *pszMessage)465 {466 AssertReturn(uContextID, VERR_INVALID_PARAMETER);467 if (RT_FAILURE(iRC))468 AssertReturn(pszMessage, VERR_INVALID_PARAMETER);469 470 LogFlowFunc(("Checking whether callback (CID=%u) needs notification iRC=%Rrc, pszMsg=%s\n",471 uContextID, iRC, pszMessage ? pszMessage : "<No message given>"));472 473 ComObjPtr<Progress> pProgress;474 {475 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);476 477 CallbackMapIterConst it = mCallbackMap.find(uContextID);478 if (it != mCallbackMap.end())479 pProgress = it->second.pProgress;480 }481 482 #if 0483 BOOL fCanceled = FALSE;484 HRESULT hRC = pProgress->COMGETTER(Canceled)(&fCanceled);485 if ( SUCCEEDED(hRC)486 && fCanceled)487 {488 /* If progress already canceled do nothing here. */489 return VINF_SUCCESS;490 }491 #endif492 493 if (pProgress)494 {495 /*496 * Assume we didn't complete to make sure we clean up even if the497 * following call fails.498 */499 BOOL fCompleted = FALSE;500 HRESULT hRC = pProgress->COMGETTER(Completed)(&fCompleted);501 if ( SUCCEEDED(hRC)502 && !fCompleted)503 {504 LogFlowFunc(("Notifying callback with CID=%u, iRC=%Rrc, pszMsg=%s\n",505 uContextID, iRC, pszMessage ? pszMessage : "<No message given>"));506 507 /*508 * To get waitForCompletion completed (unblocked) we have to notify it if necessary (only509 * cancel won't work!). This could happen if the client thread (e.g. VBoxService, thread of a spawned process)510 * is disconnecting without having the chance to sending a status message before, so we511 * have to abort here to make sure the host never hangs/gets stuck while waiting for the512 * progress object to become signalled.513 */514 if (RT_SUCCESS(iRC))515 {516 hRC = pProgress->notifyComplete(S_OK);517 }518 else519 {520 hRC = pProgress->notifyComplete(VBOX_E_IPRT_ERROR /* Must not be S_OK. */,521 COM_IIDOF(IGuest),522 Guest::getStaticComponentName(),523 pszMessage);524 }525 526 LogFlowFunc(("Notified callback with CID=%u returned %Rhrc (0x%x)\n",527 uContextID, hRC, hRC));528 }529 else530 LogFlowFunc(("Callback with CID=%u already notified\n", uContextID));531 532 /*533 * Do *not* NULL pProgress here, because waiting function like executeProcess()534 * will still rely on this object for checking whether they have to give up!535 */536 }537 /* If pProgress is not found (anymore) that's fine.538 * Might be destroyed already. */539 return S_OK;540 }541 542 /**543 * Notifies all callbacks which are assigned to a certain guest PID to set a certain544 * return/error code and an optional (error) message.545 *546 * @return IPRT status code.547 * @param uGuestPID Guest PID to find all callbacks for.548 * @param iRC Return (error) code to set for the found callbacks.549 * @param pszMessage Optional (error) message to set.550 */551 int Guest::callbackNotifyAllForPID(uint32_t uGuestPID, int iRC, const char *pszMessage)552 {553 AssertReturn(uGuestPID, VERR_INVALID_PARAMETER);554 555 int vrc = VINF_SUCCESS;556 557 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);558 559 CallbackMapIter it;560 for (it = mCallbackMap.begin(); it != mCallbackMap.end(); it++)561 {562 switch (it->second.mType)563 {564 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_START:565 break;566 567 /* When waiting for process output while the process is destroyed,568 * make sure we also destroy the actual waiting operation (internal progress object)569 * in order to not block the caller. */570 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT:571 {572 PCALLBACKDATAEXECOUT pItData = (PCALLBACKDATAEXECOUT)it->second.pvData;573 AssertPtr(pItData);574 if (pItData->u32PID == uGuestPID)575 vrc = callbackNotifyEx(it->first, iRC, pszMessage);576 break;577 }578 579 /* When waiting for injecting process input while the process is destroyed,580 * make sure we also destroy the actual waiting operation (internal progress object)581 * in order to not block the caller. */582 case VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS:583 {584 PCALLBACKDATAEXECINSTATUS pItData = (PCALLBACKDATAEXECINSTATUS)it->second.pvData;585 AssertPtr(pItData);586 if (pItData->u32PID == uGuestPID)587 vrc = callbackNotifyEx(it->first, iRC, pszMessage);588 break;589 }590 591 default:592 vrc = VERR_INVALID_PARAMETER;593 AssertMsgFailed(("Unknown callback type %d, iRC=%d, message=%s\n",594 it->second.mType, iRC, pszMessage ? pszMessage : "<No message given>"));595 break;596 }597 598 if (RT_FAILURE(vrc))599 break;600 }601 602 return vrc;603 }604 605 int Guest::callbackNotifyComplete(uint32_t uContextID)606 {607 return callbackNotifyEx(uContextID, S_OK, NULL /* No message */);608 }609 610 /**611 * Waits for a callback (using its context ID) to complete.612 *613 * @return IPRT status code.614 * @param uContextID Context ID to wait for.615 * @param lStage Stage to wait for. Specify -1 if no staging is present/required.616 * Specifying a stage is only needed if there's a multi operation progress617 * object to wait for.618 * @param lTimeout Timeout (in ms) to wait for.619 */620 int Guest::callbackWaitForCompletion(uint32_t uContextID, LONG lStage, LONG lTimeout)621 {622 AssertReturn(uContextID, VERR_INVALID_PARAMETER);623 624 /*625 * Wait for the HGCM low level callback until the process626 * has been started (or something went wrong). This is necessary to627 * get the PID.628 */629 630 int vrc = VINF_SUCCESS;631 ComPtr<IProgress> pProgress;632 {633 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);634 635 CallbackMapIterConst it = mCallbackMap.find(uContextID);636 if (it != mCallbackMap.end())637 pProgress = it->second.pProgress;638 else639 vrc = VERR_NOT_FOUND;640 }641 642 if (RT_SUCCESS(vrc))643 {644 LogFlowFunc(("Waiting for callback completion (CID=%u, Stage=%RI32, timeout=%RI32ms) ...\n",645 uContextID, lStage, lTimeout));646 HRESULT rc;647 if (lStage < 0)648 rc = pProgress->WaitForCompletion(lTimeout);649 else650 rc = pProgress->WaitForOperationCompletion((ULONG)lStage, lTimeout);651 if (SUCCEEDED(rc))652 {653 if (!callbackIsComplete(uContextID))654 vrc = callbackIsCanceled(uContextID)655 ? VERR_CANCELLED : VINF_SUCCESS;656 }657 else658 vrc = VERR_TIMEOUT;659 }660 661 LogFlowFunc(("Callback (CID=%u) completed with rc=%Rrc\n",662 uContextID, vrc));663 return vrc;664 }665 666 /**667 49 * Static callback function for receiving updates on guest control commands 668 50 * from the guest. Acts as a dispatcher for the actual class instance. … … 673 55 * 674 56 */ 57 /* static */ 675 58 DECLCALLBACK(int) Guest::notifyCtrlDispatcher(void *pvExtension, 676 59 uint32_t u32Function, … … 742 125 } 743 126 744 #ifdef DEBUG745 /* @todo Don't use legacy stuff in debug mode. Remove for final! */746 return rc;747 #endif748 749 /* Legacy handling. */750 switch (u32Function)751 {752 case GUEST_DISCONNECTED:753 {754 PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);755 AssertPtr(pCBData);756 AssertReturn(sizeof(CALLBACKDATACLIENTDISCONNECTED) == cbParms, VERR_INVALID_PARAMETER);757 AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);758 759 rc = pGuest->notifyCtrlClientDisconnected(u32Function, pCBData);760 break;761 }762 763 case GUEST_EXEC_SEND_STATUS:764 {765 PCALLBACKDATAEXECSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvParms);766 AssertPtr(pCBData);767 AssertReturn(sizeof(CALLBACKDATAEXECSTATUS) == cbParms, VERR_INVALID_PARAMETER);768 AssertReturn(CALLBACKDATAMAGIC_EXEC_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);769 770 rc = pGuest->notifyCtrlExecStatus(u32Function, pCBData);771 break;772 }773 774 case GUEST_EXEC_SEND_OUTPUT:775 {776 PCALLBACKDATAEXECOUT pCBData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvParms);777 AssertPtr(pCBData);778 AssertReturn(sizeof(CALLBACKDATAEXECOUT) == cbParms, VERR_INVALID_PARAMETER);779 AssertReturn(CALLBACKDATAMAGIC_EXEC_OUT == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);780 781 rc = pGuest->notifyCtrlExecOut(u32Function, pCBData);782 break;783 }784 785 case GUEST_EXEC_SEND_INPUT_STATUS:786 {787 PCALLBACKDATAEXECINSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECINSTATUS>(pvParms);788 AssertPtr(pCBData);789 AssertReturn(sizeof(CALLBACKDATAEXECINSTATUS) == cbParms, VERR_INVALID_PARAMETER);790 AssertReturn(CALLBACKDATAMAGIC_EXEC_IN_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);791 792 rc = pGuest->notifyCtrlExecInStatus(u32Function, pCBData);793 break;794 }795 796 default:797 /* Silently ignore not implemented functions. */798 rc = VERR_NOT_IMPLEMENTED;799 break;800 }801 802 127 LogFlowFuncLeaveRC(rc); 803 128 return rc; 804 129 } 805 806 /* Function for handling the execution start/termination notification. */807 /* Callback can be called several times. */808 int Guest::notifyCtrlExecStatus(uint32_t u32Function,809 PCALLBACKDATAEXECSTATUS pData)810 {811 AssertReturn(u32Function, VERR_INVALID_PARAMETER);812 AssertPtrReturn(pData, VERR_INVALID_PARAMETER);813 814 uint32_t uContextID = pData->hdr.u32ContextID;815 /* The context ID might be 0 in case the guest was not able to fetch816 * actual command. So we can't do much now but report an error. */817 818 /* Scope write locks as much as possible. */819 {820 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);821 822 LogFlowFunc(("Execution status (CID=%u, pData=0x%p)\n",823 uContextID, pData));824 825 PCALLBACKDATAEXECSTATUS pCallbackData =826 (PCALLBACKDATAEXECSTATUS)callbackGetUserDataMutableRaw(uContextID, NULL /* cbData */);827 if (pCallbackData)828 {829 pCallbackData->u32PID = pData->u32PID;830 pCallbackData->u32Status = pData->u32Status;831 pCallbackData->u32Flags = pData->u32Flags;832 /** @todo Copy void* buffer contents? */833 }834 /* If pCallbackData is NULL this might be an old request for which no user data835 * might exist anymore. */836 }837 838 int vrc = VINF_SUCCESS; /* Function result. */839 int rcCallback = VINF_SUCCESS; /* Callback result. */840 Utf8Str errMsg;841 842 /* Was progress canceled before? */843 bool fCanceled = callbackIsCanceled(uContextID);844 if (!fCanceled)845 {846 /* Handle process map. This needs to be done first in order to have a valid847 * map in case some callback gets notified a bit below. */848 849 uint32_t uHostPID = 0;850 851 /* Note: PIDs never get removed here in case the guest process signalled its852 * end; instead the next call of GetProcessStatus() will remove the PID853 * from the process map after we got the process' final (exit) status.854 * See waitpid() for an example. */855 if (pData->u32PID) /* Only add/change a process if it has a valid PID (>0). */856 {857 uHostPID = callbackGetHostPID(uContextID);858 Assert(uHostPID);859 860 switch (pData->u32Status)861 {862 /* Just reach through flags. */863 case PROC_STS_TES:864 case PROC_STS_TOK:865 vrc = processSetStatus(uHostPID, pData->u32PID,866 (ExecuteProcessStatus_T)pData->u32Status,867 0 /* Exit code */, pData->u32Flags);868 break;869 /* Interprete u32Flags as the guest process' exit code. */870 default:871 vrc = processSetStatus(uHostPID, pData->u32PID,872 (ExecuteProcessStatus_T)pData->u32Status,873 pData->u32Flags /* Exit code */, 0 /* Flags */);874 break;875 }876 }877 878 if (RT_SUCCESS(vrc))879 {880 /* Do progress handling. */881 switch (pData->u32Status)882 {883 case PROC_STS_STARTED:884 vrc = callbackMoveForward(uContextID, Guest::tr("Waiting for process to exit ..."));885 LogRel(("Guest process (PID %u) started\n", pData->u32PID)); /** @todo Add process name */886 break;887 888 case PROC_STS_TEN: /* Terminated normally. */889 vrc = callbackNotifyComplete(uContextID);890 LogRel(("Guest process (PID %u) exited normally (exit code: %u)\n",891 pData->u32PID, pData->u32Flags)); /** @todo Add process name */892 break;893 894 case PROC_STS_TEA: /* Terminated abnormally. */895 LogRel(("Guest process (PID %u) terminated abnormally (exit code: %u)\n",896 pData->u32PID, pData->u32Flags)); /** @todo Add process name */897 errMsg = Utf8StrFmt(Guest::tr("Process terminated abnormally with status '%u'"),898 pData->u32Flags);899 rcCallback = VERR_GENERAL_FAILURE; /** @todo */900 break;901 902 case PROC_STS_TES: /* Terminated through signal. */903 LogRel(("Guest process (PID %u) terminated through signal (exit code: %u)\n",904 pData->u32PID, pData->u32Flags)); /** @todo Add process name */905 errMsg = Utf8StrFmt(Guest::tr("Process terminated via signal with status '%u'"),906 pData->u32Flags);907 rcCallback = VERR_GENERAL_FAILURE; /** @todo */908 break;909 910 case PROC_STS_TOK:911 LogRel(("Guest process (PID %u) timed out and was killed\n", pData->u32PID)); /** @todo Add process name */912 errMsg = Utf8StrFmt(Guest::tr("Process timed out and was killed"));913 rcCallback = VERR_TIMEOUT;914 break;915 916 case PROC_STS_TOA:917 LogRel(("Guest process (PID %u) timed out and could not be killed\n", pData->u32PID)); /** @todo Add process name */918 errMsg = Utf8StrFmt(Guest::tr("Process timed out and could not be killed"));919 rcCallback = VERR_TIMEOUT;920 break;921 922 case PROC_STS_DWN:923 LogRel(("Guest process (PID %u) killed because system is shutting down\n", pData->u32PID)); /** @todo Add process name */924 /*925 * If u32Flags has ExecuteProcessFlag_IgnoreOrphanedProcesses set, we don't report an error to926 * our progress object. This is helpful for waiters which rely on the success of our progress object927 * even if the executed process was killed because the system/VBoxService is shutting down.928 *929 * In this case u32Flags contains the actual execution flags reached in via Guest::ExecuteProcess().930 */931 if (pData->u32Flags & ExecuteProcessFlag_IgnoreOrphanedProcesses)932 {933 vrc = callbackNotifyComplete(uContextID);934 }935 else936 {937 errMsg = Utf8StrFmt(Guest::tr("Process killed because system is shutting down"));938 rcCallback = VERR_CANCELLED;939 }940 break;941 942 case PROC_STS_ERROR:943 {944 Utf8Str errDetail;945 if (pData->u32PID)946 {947 errDetail = Utf8StrFmt(Guest::tr("Guest process (PID %u) could not be started because of rc=%Rrc"),948 pData->u32PID, pData->u32Flags);949 }950 else951 {952 switch (pData->u32Flags) /* u32Flags member contains the IPRT error code from guest side. */953 {954 case VERR_FILE_NOT_FOUND: /* This is the most likely error. */955 errDetail = Utf8StrFmt(Guest::tr("The specified file was not found on guest"));956 break;957 958 case VERR_PATH_NOT_FOUND:959 errDetail = Utf8StrFmt(Guest::tr("Could not resolve path to specified file was not found on guest"));960 break;961 962 case VERR_BAD_EXE_FORMAT:963 errDetail = Utf8StrFmt(Guest::tr("The specified file is not an executable format on guest"));964 break;965 966 case VERR_AUTHENTICATION_FAILURE:967 errDetail = Utf8StrFmt(Guest::tr("The specified user was not able to logon on guest"));968 break;969 970 case VERR_TIMEOUT:971 errDetail = Utf8StrFmt(Guest::tr("The guest did not respond within time"));972 break;973 974 case VERR_CANCELLED:975 errDetail = Utf8StrFmt(Guest::tr("The execution operation was canceled"));976 break;977 978 case VERR_PERMISSION_DENIED:979 errDetail = Utf8StrFmt(Guest::tr("Invalid user/password credentials"));980 break;981 982 case VERR_MAX_PROCS_REACHED:983 errDetail = Utf8StrFmt(Guest::tr("Guest process could not be started because maximum number of parallel guest processes has been reached"));984 break;985 986 default:987 errDetail = Utf8StrFmt(Guest::tr("Guest process reported error %Rrc"), pData->u32Flags);988 break;989 }990 }991 992 errMsg = Utf8StrFmt(Guest::tr("Process execution failed: "), pData->u32Flags) + errDetail;993 rcCallback = pData->u32Flags; /* Report back guest rc. */994 995 LogRel((errMsg.c_str()));996 997 break;998 }999 1000 default:1001 vrc = VERR_INVALID_PARAMETER;1002 break;1003 }1004 }1005 }1006 else1007 {1008 errMsg = Utf8StrFmt(Guest::tr("Process execution canceled"));1009 rcCallback = VERR_CANCELLED;1010 }1011 1012 /* Do we need to handle the callback error? */1013 if (RT_FAILURE(rcCallback))1014 {1015 AssertMsg(!errMsg.isEmpty(), ("Error message must not be empty!\n"));1016 1017 if (uContextID)1018 {1019 /* Notify all callbacks which are still waiting on something1020 * which is related to the current PID. */1021 if (pData->u32PID)1022 {1023 int rc2 = callbackNotifyAllForPID(pData->u32PID, rcCallback, errMsg.c_str());1024 if (RT_FAILURE(rc2))1025 {1026 LogFlowFunc(("Failed to notify other callbacks for PID=%u\n",1027 pData->u32PID));1028 if (RT_SUCCESS(vrc))1029 vrc = rc2;1030 }1031 }1032 1033 /* Let the caller know what went wrong ... */1034 int rc2 = callbackNotifyEx(uContextID, rcCallback, errMsg.c_str());1035 if (RT_FAILURE(rc2))1036 {1037 LogFlowFunc(("Failed to notify callback CID=%u for PID=%u\n",1038 uContextID, pData->u32PID));1039 if (RT_SUCCESS(vrc))1040 vrc = rc2;1041 }1042 }1043 else1044 {1045 /* Since we don't know which context exactly failed all we can do is to shutdown1046 * all contexts ... */1047 errMsg = Utf8StrFmt(Guest::tr("Client reported critical error %Rrc -- shutting down"),1048 pData->u32Flags);1049 1050 /* Cancel all callbacks. */1051 CallbackMapIter it;1052 for (it = mCallbackMap.begin(); it != mCallbackMap.end(); it++)1053 {1054 int rc2 = callbackNotifyEx(it->first, VERR_CANCELLED,1055 errMsg.c_str());1056 AssertRC(rc2);1057 }1058 }1059 1060 LogFlowFunc(("Process (CID=%u, status=%u) reported: %s\n",1061 uContextID, pData->u32Status, errMsg.c_str()));1062 }1063 LogFlowFunc(("Returned with rc=%Rrc, rcCallback=%Rrc\n",1064 vrc, rcCallback));1065 return vrc;1066 }1067 1068 /* Function for handling the execution output notification. */1069 int Guest::notifyCtrlExecOut(uint32_t u32Function,1070 PCALLBACKDATAEXECOUT pData)1071 {1072 AssertReturn(u32Function, VERR_INVALID_PARAMETER);1073 AssertPtrReturn(pData, VERR_INVALID_PARAMETER);1074 1075 uint32_t uContextID = pData->hdr.u32ContextID;1076 Assert(uContextID);1077 1078 /* Scope write locks as much as possible. */1079 {1080 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);1081 1082 LogFlowFunc(("Output status (CID=%u, pData=0x%p)\n",1083 uContextID, pData));1084 1085 PCALLBACKDATAEXECOUT pCallbackData =1086 (PCALLBACKDATAEXECOUT)callbackGetUserDataMutableRaw(uContextID, NULL /* cbData */);1087 if (pCallbackData)1088 {1089 pCallbackData->u32PID = pData->u32PID;1090 pCallbackData->u32HandleId = pData->u32HandleId;1091 pCallbackData->u32Flags = pData->u32Flags;1092 1093 /* Make sure we really got something! */1094 if ( pData->cbData1095 && pData->pvData)1096 {1097 callbackFreeUserData(pCallbackData->pvData);1098 1099 /* Allocate data buffer and copy it */1100 pCallbackData->pvData = RTMemAlloc(pData->cbData);1101 pCallbackData->cbData = pData->cbData;1102 1103 AssertReturn(pCallbackData->pvData, VERR_NO_MEMORY);1104 memcpy(pCallbackData->pvData, pData->pvData, pData->cbData);1105 }1106 else /* Nothing received ... */1107 {1108 pCallbackData->pvData = NULL;1109 pCallbackData->cbData = 0;1110 }1111 }1112 /* If pCallbackData is NULL this might be an old request for which no user data1113 * might exist anymore. */1114 }1115 1116 int vrc;1117 if (callbackIsCanceled(pData->u32PID))1118 {1119 vrc = callbackNotifyEx(uContextID, VERR_CANCELLED,1120 Guest::tr("The output operation was canceled"));1121 }1122 else1123 vrc = callbackNotifyComplete(uContextID);1124 1125 return vrc;1126 }1127 1128 /* Function for handling the execution input status notification. */1129 int Guest::notifyCtrlExecInStatus(uint32_t u32Function,1130 PCALLBACKDATAEXECINSTATUS pData)1131 {1132 AssertReturn(u32Function, VERR_INVALID_PARAMETER);1133 AssertPtrReturn(pData, VERR_INVALID_PARAMETER);1134 1135 uint32_t uContextID = pData->hdr.u32ContextID;1136 Assert(uContextID);1137 1138 /* Scope write locks as much as possible. */1139 {1140 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);1141 1142 LogFlowFunc(("Input status (CID=%u, pData=0x%p)\n",1143 uContextID, pData));1144 1145 PCALLBACKDATAEXECINSTATUS pCallbackData =1146 (PCALLBACKDATAEXECINSTATUS)callbackGetUserDataMutableRaw(uContextID, NULL /* cbData */);1147 if (pCallbackData)1148 {1149 /* Save bytes processed. */1150 pCallbackData->cbProcessed = pData->cbProcessed;1151 pCallbackData->u32Status = pData->u32Status;1152 pCallbackData->u32Flags = pData->u32Flags;1153 pCallbackData->u32PID = pData->u32PID;1154 }1155 /* If pCallbackData is NULL this might be an old request for which no user data1156 * might exist anymore. */1157 }1158 1159 return callbackNotifyComplete(uContextID);1160 }1161 1162 int Guest::notifyCtrlClientDisconnected(uint32_t u32Function,1163 PCALLBACKDATACLIENTDISCONNECTED pData)1164 {1165 /* u32Function is 0. */1166 AssertPtrReturn(pData, VERR_INVALID_PARAMETER);1167 1168 uint32_t uContextID = pData->hdr.u32ContextID;1169 Assert(uContextID);1170 1171 LogFlowFunc(("Client disconnected (CID=%u)\n,", uContextID));1172 1173 return callbackNotifyEx(uContextID, VERR_CANCELLED,1174 Guest::tr("Client disconnected"));1175 }1176 1177 uint32_t Guest::processGetGuestPID(uint32_t uHostPID)1178 {1179 AssertReturn(uHostPID, 0);1180 1181 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);1182 1183 GuestProcessMapIter it = mGuestProcessMap.find(uHostPID);1184 if (it == mGuestProcessMap.end())1185 return 0;1186 1187 return it->second.mGuestPID;1188 }1189 1190 /**1191 * Gets guest process information. Removes the process from the map1192 * after the process was marked as exited/terminated.1193 *1194 * @return IPRT status code.1195 * @param uHostPID Host PID of guest process to get status for.1196 * @param pProcess Where to store the process information. Optional.1197 * @param fRemove Flag indicating whether to remove the1198 * process from the map when process marked a1199 * exited/terminated.1200 */1201 int Guest::processGetStatus(uint32_t uHostPID, PVBOXGUESTCTRL_PROCESS pProcess,1202 bool fRemove)1203 {1204 AssertReturn(uHostPID, VERR_INVALID_PARAMETER);1205 1206 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);1207 1208 GuestProcessMapIter it = mGuestProcessMap.find(uHostPID);1209 if (it != mGuestProcessMap.end())1210 {1211 if (pProcess)1212 {1213 pProcess->mGuestPID = it->second.mGuestPID;1214 pProcess->mStatus = it->second.mStatus;1215 pProcess->mExitCode = it->second.mExitCode;1216 pProcess->mFlags = it->second.mFlags;1217 }1218 1219 /* Only remove processes from our map when they signalled their final1220 * status. */1221 if ( fRemove1222 && ( it->second.mStatus != ExecuteProcessStatus_Undefined1223 && it->second.mStatus != ExecuteProcessStatus_Started))1224 {1225 mGuestProcessMap.erase(it);1226 }1227 1228 return VINF_SUCCESS;1229 }1230 1231 return VERR_NOT_FOUND;1232 }1233 1234 /**1235 * Sets the current status of a guest process.1236 *1237 * @return IPRT status code.1238 * @param uHostPID Host PID of guest process to set status (and guest PID) for.1239 * @param uGuestPID Guest PID to assign to the host PID.1240 * @param enmStatus Current status to set.1241 * @param uExitCode Exit code (if any).1242 * @param uFlags Additional flags.1243 */1244 int Guest::processSetStatus(uint32_t uHostPID, uint32_t uGuestPID,1245 ExecuteProcessStatus_T enmStatus, uint32_t uExitCode, uint32_t uFlags)1246 {1247 AssertReturn(uHostPID, VERR_INVALID_PARAMETER);1248 /* Assigning a guest PID is optional. */1249 1250 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);1251 1252 GuestProcessMapIter it = mGuestProcessMap.find(uHostPID);1253 if (it != mGuestProcessMap.end())1254 {1255 it->second.mGuestPID = uGuestPID;1256 it->second.mStatus = enmStatus;1257 it->second.mExitCode = uExitCode;1258 it->second.mFlags = uFlags;1259 }1260 else1261 {1262 VBOXGUESTCTRL_PROCESS process;1263 1264 /* uGuestPID is optional -- the caller could call this function1265 * before the guest process actually was started and a (valid) guest PID1266 * was returned. */1267 process.mGuestPID = uGuestPID;1268 process.mStatus = enmStatus;1269 process.mExitCode = uExitCode;1270 process.mFlags = uFlags;1271 1272 mGuestProcessMap[uHostPID] = process;1273 }1274 1275 return VINF_SUCCESS;1276 }1277 1278 HRESULT Guest::setErrorCompletion(int rc)1279 {1280 HRESULT hRC;1281 if (rc == VERR_NOT_FOUND)1282 hRC = setErrorNoLog(VBOX_E_VM_ERROR,1283 tr("VMM device is not available (is the VM running?)"));1284 else if (rc == VERR_CANCELLED)1285 hRC = setErrorNoLog(VBOX_E_IPRT_ERROR,1286 tr("Process execution has been canceled"));1287 else if (rc == VERR_TIMEOUT)1288 hRC= setErrorNoLog(VBOX_E_IPRT_ERROR,1289 tr("The guest did not respond within time"));1290 else1291 hRC = setErrorNoLog(E_UNEXPECTED,1292 tr("Waiting for completion failed with error %Rrc"), rc);1293 return hRC;1294 }1295 1296 HRESULT Guest::setErrorFromProgress(ComPtr<IProgress> pProgress)1297 {1298 BOOL fCompleted;1299 HRESULT rc = pProgress->COMGETTER(Completed)(&fCompleted);1300 ComAssertComRC(rc);1301 1302 LONG rcProc = S_OK;1303 Utf8Str strError;1304 1305 if (!fCompleted)1306 {1307 BOOL fCanceled;1308 rc = pProgress->COMGETTER(Canceled)(&fCanceled);1309 ComAssertComRC(rc);1310 1311 strError = fCanceled ? Utf8StrFmt(Guest::tr("Process execution was canceled"))1312 : Utf8StrFmt(Guest::tr("Process neither completed nor canceled; this shouldn't happen"));1313 }1314 else1315 {1316 rc = pProgress->COMGETTER(ResultCode)(&rcProc);1317 ComAssertComRC(rc);1318 1319 if (FAILED(rcProc))1320 {1321 ProgressErrorInfo info(pProgress);1322 strError = info.getText();1323 }1324 }1325 1326 if (FAILED(rcProc))1327 {1328 AssertMsg(!strError.isEmpty(), ("Error message must not be empty!\n"));1329 return setErrorInternal(rcProc,1330 this->getClassIID(),1331 this->getComponentName(),1332 strError,1333 false /* aWarning */,1334 false /* aLogIt */);1335 }1336 1337 return S_OK;1338 }1339 1340 HRESULT Guest::setErrorHGCM(int rc)1341 {1342 HRESULT hRC;1343 if (rc == VERR_INVALID_VM_HANDLE)1344 hRC = setErrorNoLog(VBOX_E_VM_ERROR,1345 tr("VMM device is not available (is the VM running?)"));1346 else if (rc == VERR_NOT_FOUND)1347 hRC = setErrorNoLog(VBOX_E_IPRT_ERROR,1348 tr("The guest execution service is not ready (yet)"));1349 else if (rc == VERR_HGCM_SERVICE_NOT_FOUND)1350 hRC= setErrorNoLog(VBOX_E_IPRT_ERROR,1351 tr("The guest execution service is not available"));1352 else /* HGCM call went wrong. */1353 hRC = setErrorNoLog(E_UNEXPECTED,1354 tr("The HGCM call failed with error %Rrc"), rc);1355 return hRC;1356 }1357 130 #endif /* VBOX_WITH_GUEST_CONTROL */ 1358 1359 STDMETHODIMP Guest::ExecuteProcess(IN_BSTR aCommand, ULONG aFlags,1360 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),1361 IN_BSTR aUsername, IN_BSTR aPassword,1362 ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress)1363 {1364 /** @todo r=bird: Eventually we should clean up all the timeout parameters1365 * in the API and have the same way of specifying infinite waits! */1366 #ifndef VBOX_WITH_GUEST_CONTROL1367 ReturnComNotImplemented();1368 #else /* VBOX_WITH_GUEST_CONTROL */1369 using namespace guestControl;1370 1371 CheckComArgStrNotEmptyOrNull(aCommand);1372 CheckComArgOutPointerValid(aPID);1373 CheckComArgOutPointerValid(aProgress);1374 1375 /* Do not allow anonymous executions (with system rights). */1376 if (RT_UNLIKELY((aUsername) == NULL || *(aUsername) == '\0'))1377 return setError(E_INVALIDARG, tr("No user name specified"));1378 1379 LogRel(("Executing guest process \"%s\" as user \"%s\" ...\n",1380 Utf8Str(aCommand).c_str(), Utf8Str(aUsername).c_str()));1381 1382 return executeProcessInternal(aCommand, aFlags, ComSafeArrayInArg(aArguments),1383 ComSafeArrayInArg(aEnvironment),1384 aUsername, aPassword, aTimeoutMS, aPID, aProgress, NULL /* rc */);1385 #endif1386 }1387 1388 #ifdef VBOX_WITH_GUEST_CONTROL1389 /**1390 * Executes and waits for an internal tool (that is, a tool which is integrated into1391 * VBoxService, beginning with "vbox_" (e.g. "vbox_ls")) to finish its operation.1392 *1393 * @return HRESULT1394 * @param aTool Name of tool to execute.1395 * @param aDescription Friendly description of the operation.1396 * @param aFlags Execution flags.1397 * @param aUsername Username to execute tool under.1398 * @param aPassword The user's password.1399 * @param uFlagsToAdd ExecuteProcessFlag flags to add to the execution operation.1400 * @param aProgress Pointer which receives the tool's progress object. Optional.1401 * @param aPID Pointer which receives the tool's PID. Optional.1402 */1403 HRESULT Guest::executeAndWaitForTool(IN_BSTR aTool, IN_BSTR aDescription,1404 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),1405 IN_BSTR aUsername, IN_BSTR aPassword,1406 ULONG uFlagsToAdd,1407 GuestCtrlStreamObjects *pObjStdOut, GuestCtrlStreamObjects *pObjStdErr,1408 IProgress **aProgress, ULONG *aPID)1409 {1410 ComPtr<IProgress> pProgress;1411 ULONG uPID;1412 ULONG uFlags = ExecuteProcessFlag_Hidden;1413 if (uFlagsToAdd)1414 uFlags |= uFlagsToAdd;1415 1416 bool fParseOutput = false;1417 if ( ( (uFlags & ExecuteProcessFlag_WaitForStdOut)1418 && pObjStdOut)1419 || ( (uFlags & ExecuteProcessFlag_WaitForStdErr)1420 && pObjStdErr))1421 {1422 fParseOutput = true;1423 }1424 1425 HRESULT hr = ExecuteProcess(aTool,1426 uFlags,1427 ComSafeArrayInArg(aArguments),1428 ComSafeArrayInArg(aEnvironment),1429 aUsername, aPassword,1430 0 /* No timeout */,1431 &uPID, pProgress.asOutParam());1432 if (SUCCEEDED(hr))1433 {1434 /* Wait for tool being started. */1435 hr = pProgress->WaitForOperationCompletion( 0 /* Stage, starting the process */,1436 -1 /* No timeout */);1437 }1438 1439 if ( SUCCEEDED(hr)1440 && !(uFlags & ExecuteProcessFlag_WaitForProcessStartOnly))1441 {1442 if (!fParseOutput)1443 {1444 if ( !(uFlags & ExecuteProcessFlag_WaitForStdOut)1445 && !(uFlags & ExecuteProcessFlag_WaitForStdErr))1446 {1447 hr = executeWaitForExit(uPID, pProgress, 0 /* No timeout */);1448 }1449 }1450 else1451 {1452 BOOL fCompleted;1453 while ( SUCCEEDED(pProgress->COMGETTER(Completed)(&fCompleted))1454 && !fCompleted)1455 {1456 BOOL fCanceled;1457 hr = pProgress->COMGETTER(Canceled)(&fCanceled);1458 AssertComRC(hr);1459 if (fCanceled)1460 {1461 hr = setErrorNoLog(VBOX_E_IPRT_ERROR,1462 tr("%s was cancelled"), Utf8Str(aDescription).c_str());1463 break;1464 }1465 1466 if ( (uFlags & ExecuteProcessFlag_WaitForStdOut)1467 && pObjStdOut)1468 {1469 hr = executeStreamParse(uPID, ProcessOutputFlag_None /* StdOut */, *pObjStdOut);1470 }1471 1472 if ( (uFlags & ExecuteProcessFlag_WaitForStdErr)1473 && pObjStdErr)1474 {1475 hr = executeStreamParse(uPID, ProcessOutputFlag_StdErr, *pObjStdErr);1476 }1477 1478 if (FAILED(hr))1479 break;1480 }1481 }1482 }1483 1484 if (SUCCEEDED(hr))1485 {1486 if (aProgress)1487 {1488 /* Return the progress to the caller. */1489 pProgress.queryInterfaceTo(aProgress);1490 }1491 else if (!pProgress.isNull())1492 pProgress.setNull();1493 1494 if (aPID)1495 *aPID = uPID;1496 }1497 1498 return hr;1499 }1500 1501 /**1502 * TODO1503 *1504 * @return HRESULT1505 * @param aObjName1506 * @param pStreamBlock1507 * @param pObjInfo1508 * @param enmAddAttribs1509 */1510 int Guest::executeStreamQueryFsObjInfo(IN_BSTR aObjName,1511 GuestProcessStreamBlock &streamBlock,1512 PRTFSOBJINFO pObjInfo,1513 RTFSOBJATTRADD enmAddAttribs)1514 {1515 Utf8Str Utf8ObjName(aObjName);1516 int64_t iVal;1517 int rc = streamBlock.GetInt64Ex("st_size", &iVal);1518 if (RT_SUCCESS(rc))1519 pObjInfo->cbObject = iVal;1520 /** @todo Add more stuff! */1521 return rc;1522 }1523 1524 /**1525 * Tries to drain the guest's output and fill it into1526 * a guest process stream object for later usage.1527 *1528 * @todo What's about specifying stderr?1529 * @return IPRT status code.1530 * @param aPID PID of process to get the output from.1531 * @param aFlags Which stream to drain (stdout or stderr).1532 * @param pStream Pointer to guest process stream to fill. If NULL,1533 * data goes to /dev/null.1534 */1535 int Guest::executeStreamDrain(ULONG aPID, ULONG aFlags, GuestProcessStream *pStream)1536 {1537 AssertReturn(aPID, VERR_INVALID_PARAMETER);1538 /* pStream is optional. */1539 1540 int rc = VINF_SUCCESS;1541 for (;;)1542 {1543 SafeArray<BYTE> aData;1544 HRESULT hr = getProcessOutputInternal(aPID, aFlags,1545 0 /* Infinite timeout */,1546 _64K, ComSafeArrayAsOutParam(aData), &rc);1547 if (RT_SUCCESS(rc))1548 {1549 if ( pStream1550 && aData.size())1551 {1552 rc = pStream->AddData(aData.raw(), aData.size());1553 if (RT_UNLIKELY(RT_FAILURE(rc)))1554 break;1555 }1556 1557 continue; /* Try one more time. */1558 }1559 else /* No more output and/or error! */1560 {1561 if ( rc == VERR_NOT_FOUND1562 || rc == VERR_BROKEN_PIPE)1563 {1564 rc = VINF_SUCCESS;1565 }1566 1567 break;1568 }1569 }1570 1571 return rc;1572 }1573 1574 /**1575 * Tries to retrieve the next stream block of a given stream and1576 * drains the process output only as much as needed to get this next1577 * stream block.1578 *1579 * @return IPRT status code.1580 * @param ulPID1581 * @param ulFlags1582 * @param stream1583 * @param streamBlock1584 */1585 int Guest::executeStreamGetNextBlock(ULONG ulPID,1586 ULONG ulFlags,1587 GuestProcessStream &stream,1588 GuestProcessStreamBlock &streamBlock)1589 {1590 AssertReturn(!streamBlock.GetCount(), VERR_INVALID_PARAMETER);1591 1592 LogFlowFunc(("Getting next stream block of PID=%u, Flags=%u; cbStrmSize=%u, cbStrmOff=%u\n",1593 ulPID, ulFlags, stream.GetSize(), stream.GetOffset()));1594 1595 int rc;1596 1597 uint32_t cPairs = 0;1598 bool fDrainStream = true;1599 1600 do1601 {1602 rc = stream.ParseBlock(streamBlock);1603 LogFlowFunc(("Parsing block rc=%Rrc, strmBlockCnt=%ld\n",1604 rc, streamBlock.GetCount()));1605 1606 if (RT_FAILURE(rc)) /* More data needed or empty buffer? */1607 {1608 if (fDrainStream)1609 {1610 SafeArray<BYTE> aData;1611 HRESULT hr = getProcessOutputInternal(ulPID, ulFlags,1612 0 /* Infinite timeout */,1613 _64K, ComSafeArrayAsOutParam(aData), &rc);1614 if (SUCCEEDED(hr))1615 {1616 LogFlowFunc(("Got %ld bytes of additional data\n", aData.size()));1617 1618 if (RT_FAILURE(rc))1619 {1620 if (rc == VERR_BROKEN_PIPE)1621 rc = VINF_SUCCESS; /* No more data because process already ended. */1622 break;1623 }1624 1625 if (aData.size())1626 {1627 rc = stream.AddData(aData.raw(), aData.size());1628 if (RT_UNLIKELY(RT_FAILURE(rc)))1629 break;1630 }1631 1632 /* Reset found pairs to not break out too early and let all the new1633 * data to be parsed as well. */1634 cPairs = 0;1635 continue; /* Try one more time. */1636 }1637 else1638 {1639 LogFlowFunc(("Getting output returned hr=%Rhrc\n", hr));1640 1641 /* No more output to drain from stream. */1642 if (rc == VERR_NOT_FOUND)1643 {1644 fDrainStream = false;1645 continue;1646 }1647 1648 break;1649 }1650 }1651 else1652 {1653 /* We haved drained the stream as much as we can and reached the1654 * end of our stream buffer -- that means that there simply is no1655 * stream block anymore, which is ok. */1656 if (rc == VERR_NO_DATA)1657 rc = VINF_SUCCESS;1658 break;1659 }1660 }1661 else1662 cPairs = streamBlock.GetCount();1663 }1664 while (!cPairs);1665 1666 LogFlowFunc(("Returned with strmBlockCnt=%ld, cPairs=%ld, rc=%Rrc\n",1667 streamBlock.GetCount(), cPairs, rc));1668 return rc;1669 }1670 1671 /**1672 * Tries to get the next upcoming value block from a started guest process1673 * by first draining its output and then processing the received guest stream.1674 *1675 * @return IPRT status code.1676 * @param ulPID PID of process to get/parse the output from.1677 * @param ulFlags ?1678 * @param stream Reference to process stream object to use.1679 * @param streamBlock Reference that receives the next stream block data.1680 *1681 */1682 int Guest::executeStreamParseNextBlock(ULONG ulPID,1683 ULONG ulFlags,1684 GuestProcessStream &stream,1685 GuestProcessStreamBlock &streamBlock)1686 {1687 AssertReturn(!streamBlock.GetCount(), VERR_INVALID_PARAMETER);1688 1689 int rc;1690 do1691 {1692 rc = stream.ParseBlock(streamBlock);1693 if (RT_FAILURE(rc))1694 break;1695 }1696 while (!streamBlock.GetCount());1697 1698 return rc;1699 }1700 1701 /**1702 * Gets output from a formerly started guest process, tries to parse all of its guest1703 * stream (as long as data is available) and returns a stream object which can contain1704 * multiple stream blocks (which in turn then can contain key=value pairs).1705 *1706 * @return HRESULT1707 * @param ulPID PID of process to get/parse the output from.1708 * @param ulFlags ?1709 * @param streamObjects Reference to a guest stream object structure for1710 * storing the parsed data.1711 */1712 HRESULT Guest::executeStreamParse(ULONG ulPID, ULONG ulFlags, GuestCtrlStreamObjects &streamObjects)1713 {1714 GuestProcessStream stream;1715 int rc = executeStreamDrain(ulPID, ulFlags, &stream);1716 if (RT_SUCCESS(rc))1717 {1718 do1719 {1720 /* Try to parse the stream output we gathered until now. If we still need more1721 * data the parsing routine will tell us and we just do another poll round. */1722 GuestProcessStreamBlock curBlock;1723 rc = executeStreamParseNextBlock(ulPID, ulFlags, stream, curBlock);1724 if (RT_SUCCESS(rc))1725 streamObjects.push_back(curBlock);1726 } while (RT_SUCCESS(rc));1727 1728 if (rc == VERR_NO_DATA) /* End of data reached. */1729 rc = VINF_SUCCESS;1730 }1731 1732 if (RT_FAILURE(rc))1733 return setError(VBOX_E_IPRT_ERROR,1734 tr("Error while parsing guest output (%Rrc)"), rc);1735 return rc;1736 }1737 1738 /**1739 * Waits for a fomerly started guest process to exit using its progress1740 * object and returns its final status. Returns E_ABORT if guest process1741 * was canceled.1742 *1743 * @return IPRT status code.1744 * @param uPID PID of guest process to wait for.1745 * @param pProgress Progress object to wait for.1746 * @param uTimeoutMS Timeout (in ms) for waiting; use 0 for1747 * an indefinite timeout.1748 * @param pRetStatus Pointer where to store the final process1749 * status. Optional.1750 * @param puRetExitCode Pointer where to store the final process1751 * exit code. Optional.1752 */1753 HRESULT Guest::executeWaitForExit(ULONG uPID, ComPtr<IProgress> pProgress, ULONG uTimeoutMS)1754 {1755 HRESULT rc = S_OK;1756 1757 BOOL fCanceled = FALSE;1758 if ( SUCCEEDED(pProgress->COMGETTER(Canceled(&fCanceled)))1759 && fCanceled)1760 {1761 return E_ABORT;1762 }1763 1764 BOOL fCompleted = FALSE;1765 if ( SUCCEEDED(pProgress->COMGETTER(Completed(&fCompleted)))1766 && !fCompleted)1767 {1768 rc = pProgress->WaitForCompletion( !uTimeoutMS1769 ? -1 /* No timeout */1770 : uTimeoutMS);1771 if (FAILED(rc))1772 rc = setError(VBOX_E_IPRT_ERROR,1773 tr("Waiting for guest process to end failed (%Rhrc)"), rc);1774 }1775 1776 return rc;1777 }1778 1779 /**1780 * Does the actual guest process execution, internal function.1781 *1782 * @return HRESULT1783 * @param aCommand Command line to execute.1784 * @param aFlags Execution flags.1785 * @param Username Username to execute the process with.1786 * @param aPassword The user's password.1787 * @param aTimeoutMS Timeout (in ms) to wait for the execution operation.1788 * @param aPID Pointer that receives the guest process' PID.1789 * @param aProgress Pointer that receives the guest process' progress object.1790 * @param pRC Pointer that receives the internal IPRT return code. Optional.1791 */1792 HRESULT Guest::executeProcessInternal(IN_BSTR aCommand, ULONG aFlags,1793 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),1794 IN_BSTR aUsername, IN_BSTR aPassword,1795 ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress, int *pRC)1796 {1797 /** @todo r=bird: Eventually we should clean up all the timeout parameters1798 * in the API and have the same way of specifying infinite waits! */1799 using namespace guestControl;1800 1801 AutoCaller autoCaller(this);1802 if (FAILED(autoCaller.rc())) return autoCaller.rc();1803 1804 /* Validate flags. */1805 if (aFlags != ExecuteProcessFlag_None)1806 {1807 if ( !(aFlags & ExecuteProcessFlag_IgnoreOrphanedProcesses)1808 && !(aFlags & ExecuteProcessFlag_WaitForProcessStartOnly)1809 && !(aFlags & ExecuteProcessFlag_Hidden)1810 && !(aFlags & ExecuteProcessFlag_NoProfile)1811 && !(aFlags & ExecuteProcessFlag_WaitForStdOut)1812 && !(aFlags & ExecuteProcessFlag_WaitForStdErr))1813 {1814 if (pRC)1815 *pRC = VERR_INVALID_PARAMETER;1816 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);1817 }1818 }1819 1820 HRESULT rc = S_OK;1821 1822 try1823 {1824 /*1825 * Create progress object. Note that this is a multi operation1826 * object to perform the following steps:1827 * - Operation 1 (0): Create/start process.1828 * - Operation 2 (1): Wait for process to exit.1829 * If this progress completed successfully (S_OK), the process1830 * started and exited normally. In any other case an error/exception1831 * occurred.1832 */1833 ComObjPtr <Progress> pProgress;1834 rc = pProgress.createObject();1835 if (SUCCEEDED(rc))1836 {1837 rc = pProgress->init(static_cast<IGuest*>(this),1838 Bstr(tr("Executing process")).raw(),1839 TRUE,1840 2, /* Number of operations. */1841 Bstr(tr("Starting process ...")).raw()); /* Description of first stage. */1842 }1843 ComAssertComRC(rc);1844 1845 /*1846 * Prepare process execution.1847 */1848 int vrc = VINF_SUCCESS;1849 Utf8Str Utf8Command(aCommand);1850 1851 /* Adjust timeout. If set to 0, we define1852 * an infinite timeout. */1853 if (aTimeoutMS == 0)1854 aTimeoutMS = UINT32_MAX;1855 1856 /* Prepare arguments. */1857 char **papszArgv = NULL;1858 uint32_t uNumArgs = 0;1859 if (aArguments)1860 {1861 com::SafeArray<IN_BSTR> args(ComSafeArrayInArg(aArguments));1862 uNumArgs = args.size();1863 papszArgv = (char**)RTMemAlloc(sizeof(char*) * (uNumArgs + 1));1864 AssertReturn(papszArgv, E_OUTOFMEMORY);1865 for (unsigned i = 0; RT_SUCCESS(vrc) && i < uNumArgs; i++)1866 vrc = RTUtf16ToUtf8(args[i], &papszArgv[i]);1867 papszArgv[uNumArgs] = NULL;1868 }1869 1870 Utf8Str Utf8UserName(aUsername);1871 Utf8Str Utf8Password(aPassword);1872 uint32_t uHostPID = 0;1873 1874 if (RT_SUCCESS(vrc))1875 {1876 uint32_t uContextID = 0;1877 1878 char *pszArgs = NULL;1879 if (uNumArgs > 0)1880 vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);1881 if (RT_SUCCESS(vrc))1882 {1883 uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */1884 1885 /* Prepare environment. */1886 void *pvEnv = NULL;1887 uint32_t uNumEnv = 0;1888 uint32_t cbEnv = 0;1889 if (aEnvironment)1890 {1891 com::SafeArray<IN_BSTR> env(ComSafeArrayInArg(aEnvironment));1892 1893 for (unsigned i = 0; i < env.size(); i++)1894 {1895 vrc = prepareExecuteEnv(Utf8Str(env[i]).c_str(), &pvEnv, &cbEnv, &uNumEnv);1896 if (RT_FAILURE(vrc))1897 break;1898 }1899 }1900 1901 if (RT_SUCCESS(vrc))1902 {1903 VBOXGUESTCTRL_CALLBACK callback;1904 vrc = callbackInit(&callback, VBOXGUESTCTRLCALLBACKTYPE_EXEC_START, pProgress);1905 if (RT_SUCCESS(vrc))1906 vrc = callbackAdd(&callback, &uContextID);1907 1908 if (RT_SUCCESS(vrc))1909 {1910 VBOXHGCMSVCPARM paParms[15];1911 int i = 0;1912 paParms[i++].setUInt32(uContextID);1913 paParms[i++].setPointer((void*)Utf8Command.c_str(), (uint32_t)Utf8Command.length() + 1);1914 paParms[i++].setUInt32(aFlags);1915 paParms[i++].setUInt32(uNumArgs);1916 paParms[i++].setPointer((void*)pszArgs, cbArgs);1917 paParms[i++].setUInt32(uNumEnv);1918 paParms[i++].setUInt32(cbEnv);1919 paParms[i++].setPointer((void*)pvEnv, cbEnv);1920 paParms[i++].setPointer((void*)Utf8UserName.c_str(), (uint32_t)Utf8UserName.length() + 1);1921 paParms[i++].setPointer((void*)Utf8Password.c_str(), (uint32_t)Utf8Password.length() + 1);1922 1923 /*1924 * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout1925 * until the process was started - the process itself then gets an infinite timeout for execution.1926 * This is handy when we want to start a process inside a worker thread within a certain timeout1927 * but let the started process perform lengthly operations then.1928 */1929 if (aFlags & ExecuteProcessFlag_WaitForProcessStartOnly)1930 paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);1931 else1932 paParms[i++].setUInt32(aTimeoutMS);1933 1934 VMMDev *pVMMDev = NULL;1935 {1936 /* Make sure mParent is valid, so set the read lock while using.1937 * Do not keep this lock while doing the actual call, because in the meanwhile1938 * another thread could request a write lock which would be a bad idea ... */1939 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);1940 1941 /* Forward the information to the VMM device. */1942 AssertPtr(mParent);1943 pVMMDev = mParent->getVMMDev();1944 }1945 1946 if (pVMMDev)1947 {1948 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));1949 vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,1950 i, paParms);1951 }1952 else1953 vrc = VERR_INVALID_VM_HANDLE;1954 }1955 RTMemFree(pvEnv);1956 }1957 RTStrFree(pszArgs);1958 }1959 1960 if (RT_SUCCESS(vrc))1961 {1962 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);1963 1964 /*1965 * Generate a host-driven PID so that we immediately can return to the caller and1966 * don't need to wait until the guest started the desired process to return the1967 * PID generated by the guest OS.1968 *1969 * The guest PID will later be mapped to the host PID for later lookup.1970 */1971 vrc = VERR_NOT_FOUND; /* We did not find a host PID yet ... */1972 1973 uint32_t uTries = 0;1974 for (;;)1975 {1976 /* Create a new context ID ... */1977 uHostPID = ASMAtomicIncU32(&mNextHostPID);1978 if (uHostPID == UINT32_MAX)1979 ASMAtomicUoWriteU32(&mNextHostPID, 1000);1980 /* Is the host PID already used? Try next PID ... */1981 GuestProcessMapIter it = mGuestProcessMap.find(uHostPID);1982 if (it == mGuestProcessMap.end())1983 {1984 /* Host PID not used (anymore), we're done here ... */1985 vrc = VINF_SUCCESS;1986 break;1987 }1988 1989 if (++uTries == UINT32_MAX)1990 break; /* Don't try too hard. */1991 }1992 1993 if (RT_SUCCESS(vrc))1994 vrc = processSetStatus(uHostPID, 0 /* No guest PID yet */,1995 ExecuteProcessStatus_Undefined /* Process not started yet */,1996 0 /* uExitCode */, 0 /* uFlags */);1997 1998 if (RT_SUCCESS(vrc))1999 vrc = callbackAssignHostPID(uContextID, uHostPID);2000 }2001 else2002 rc = setErrorHGCM(vrc);2003 2004 for (unsigned i = 0; i < uNumArgs; i++)2005 RTMemFree(papszArgv[i]);2006 RTMemFree(papszArgv);2007 2008 if (RT_FAILURE(vrc))2009 rc = VBOX_E_IPRT_ERROR;2010 }2011 2012 if (SUCCEEDED(rc))2013 {2014 /* Return host PID. */2015 *aPID = uHostPID;2016 2017 /* Return the progress to the caller. */2018 pProgress.queryInterfaceTo(aProgress);2019 }2020 else2021 {2022 if (!pRC) /* Skip logging internal calls. */2023 LogRel(("Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",2024 Utf8Command.c_str(), Utf8UserName.c_str(), vrc));2025 }2026 2027 if (pRC)2028 *pRC = vrc;2029 }2030 catch (std::bad_alloc &)2031 {2032 rc = E_OUTOFMEMORY;2033 if (pRC)2034 *pRC = VERR_NO_MEMORY;2035 }2036 return rc;2037 }2038 #endif /* VBOX_WITH_GUEST_CONTROL */2039 2040 STDMETHODIMP Guest::SetProcessInput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, ComSafeArrayIn(BYTE, aData), ULONG *aBytesWritten)2041 {2042 #ifndef VBOX_WITH_GUEST_CONTROL2043 ReturnComNotImplemented();2044 #else /* VBOX_WITH_GUEST_CONTROL */2045 using namespace guestControl;2046 2047 CheckComArgExpr(aPID, aPID > 0);2048 CheckComArgOutPointerValid(aBytesWritten);2049 2050 /* Validate flags. */2051 if (aFlags)2052 {2053 if (!(aFlags & ProcessInputFlag_EndOfFile))2054 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);2055 }2056 2057 AutoCaller autoCaller(this);2058 if (FAILED(autoCaller.rc())) return autoCaller.rc();2059 2060 HRESULT rc = S_OK;2061 2062 try2063 {2064 VBOXGUESTCTRL_PROCESS process;2065 int vrc = processGetStatus(aPID, &process, false /* Don't remove */);2066 if (RT_SUCCESS(vrc))2067 {2068 /* PID exists; check if process is still running. */2069 if (process.mStatus != ExecuteProcessStatus_Started)2070 rc = setError(VBOX_E_IPRT_ERROR,2071 Guest::tr("Cannot inject input to a not running process (PID %u)"), aPID);2072 }2073 else2074 rc = setError(VBOX_E_IPRT_ERROR,2075 Guest::tr("Cannot inject input to a non-existent process (PID %u)"), aPID);2076 2077 if (RT_SUCCESS(vrc))2078 {2079 uint32_t uContextID = 0;2080 2081 uint32_t uGuestPID = processGetGuestPID(aPID);2082 Assert(uGuestPID);2083 2084 /*2085 * Create progress object.2086 * This progress object, compared to the one in executeProgress() above,2087 * is only single-stage local and is used to determine whether the operation2088 * finished or got canceled.2089 */2090 ComObjPtr <Progress> pProgress;2091 rc = pProgress.createObject();2092 if (SUCCEEDED(rc))2093 {2094 rc = pProgress->init(static_cast<IGuest*>(this),2095 Bstr(tr("Setting input for process")).raw(),2096 TRUE /* Cancelable */);2097 }2098 if (FAILED(rc)) throw rc;2099 ComAssert(!pProgress.isNull());2100 2101 /* Adjust timeout. */2102 if (aTimeoutMS == 0)2103 aTimeoutMS = UINT32_MAX;2104 2105 VBOXGUESTCTRL_CALLBACK callback;2106 vrc = callbackInit(&callback, VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS, pProgress);2107 if (RT_SUCCESS(vrc))2108 {2109 PCALLBACKDATAEXECINSTATUS pData = (PCALLBACKDATAEXECINSTATUS)callback.pvData;2110 2111 /* Save PID + output flags for later use. */2112 pData->u32PID = uGuestPID;2113 pData->u32Flags = aFlags;2114 }2115 2116 if (RT_SUCCESS(vrc))2117 vrc = callbackAdd(&callback, &uContextID);2118 2119 if (RT_SUCCESS(vrc))2120 {2121 com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(aData));2122 uint32_t cbSize = sfaData.size();2123 2124 VBOXHGCMSVCPARM paParms[6];2125 int i = 0;2126 paParms[i++].setUInt32(uContextID);2127 paParms[i++].setUInt32(uGuestPID);2128 paParms[i++].setUInt32(aFlags);2129 paParms[i++].setPointer(sfaData.raw(), cbSize);2130 paParms[i++].setUInt32(cbSize);2131 2132 {2133 VMMDev *pVMMDev = NULL;2134 {2135 /* Make sure mParent is valid, so set the read lock while using.2136 * Do not keep this lock while doing the actual call, because in the meanwhile2137 * another thread could request a write lock which would be a bad idea ... */2138 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);2139 2140 /* Forward the information to the VMM device. */2141 AssertPtr(mParent);2142 pVMMDev = mParent->getVMMDev();2143 }2144 2145 if (pVMMDev)2146 {2147 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));2148 vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_SET_INPUT,2149 i, paParms);2150 if (RT_FAILURE(vrc))2151 rc = setErrorHGCM(vrc);2152 }2153 }2154 }2155 2156 if (RT_SUCCESS(vrc))2157 {2158 LogFlowFunc(("Waiting for HGCM callback ...\n"));2159 2160 /*2161 * Wait for getting back the input response from the guest.2162 */2163 vrc = callbackWaitForCompletion(uContextID, -1 /* No staging required */, aTimeoutMS);2164 if (RT_SUCCESS(vrc))2165 {2166 PCALLBACKDATAEXECINSTATUS pExecStatusIn;2167 vrc = callbackGetUserData(uContextID, NULL /* We know the type. */,2168 (void**)&pExecStatusIn, NULL /* Don't need the size. */);2169 if (RT_SUCCESS(vrc))2170 {2171 AssertPtr(pExecStatusIn);2172 switch (pExecStatusIn->u32Status)2173 {2174 case INPUT_STS_WRITTEN:2175 *aBytesWritten = pExecStatusIn->cbProcessed;2176 break;2177 2178 case INPUT_STS_ERROR:2179 rc = setError(VBOX_E_IPRT_ERROR,2180 tr("Client reported error %Rrc while processing input data"),2181 pExecStatusIn->u32Flags);2182 break;2183 2184 case INPUT_STS_TERMINATED:2185 rc = setError(VBOX_E_IPRT_ERROR,2186 tr("Client terminated while processing input data"));2187 break;2188 2189 case INPUT_STS_OVERFLOW:2190 rc = setError(VBOX_E_IPRT_ERROR,2191 tr("Client reported buffer overflow while processing input data"));2192 break;2193 2194 default:2195 /*AssertReleaseMsgFailed(("Client reported unknown input error, status=%u, flags=%u\n",2196 pExecStatusIn->u32Status, pExecStatusIn->u32Flags));*/2197 break;2198 }2199 2200 callbackFreeUserData(pExecStatusIn);2201 }2202 else2203 {2204 rc = setErrorNoLog(VBOX_E_IPRT_ERROR,2205 tr("Unable to retrieve process input status data"));2206 }2207 }2208 else2209 rc = setErrorCompletion(vrc);2210 }2211 2212 {2213 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);2214 2215 /* The callback isn't needed anymore -- just was kept locally. */2216 callbackRemove(uContextID);2217 }2218 2219 /* Cleanup. */2220 if (!pProgress.isNull())2221 pProgress->uninit();2222 pProgress.setNull();2223 }2224 }2225 catch (std::bad_alloc &)2226 {2227 rc = E_OUTOFMEMORY;2228 }2229 return rc;2230 #endif2231 }2232 2233 STDMETHODIMP Guest::GetProcessOutput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, LONG64 aSize, ComSafeArrayOut(BYTE, aData))2234 {2235 #ifndef VBOX_WITH_GUEST_CONTROL2236 ReturnComNotImplemented();2237 #else /* VBOX_WITH_GUEST_CONTROL */2238 using namespace guestControl;2239 2240 return getProcessOutputInternal(aPID, aFlags, aTimeoutMS,2241 aSize, ComSafeArrayOutArg(aData), NULL /* rc */);2242 #endif2243 }2244 2245 HRESULT Guest::getProcessOutputInternal(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS,2246 LONG64 aSize, ComSafeArrayOut(BYTE, aData), int *pRC)2247 {2248 /** @todo r=bird: Eventually we should clean up all the timeout parameters2249 * in the API and have the same way of specifying infinite waits! */2250 #ifndef VBOX_WITH_GUEST_CONTROL2251 ReturnComNotImplemented();2252 #else /* VBOX_WITH_GUEST_CONTROL */2253 using namespace guestControl;2254 2255 CheckComArgExpr(aPID, aPID > 0);2256 if (aSize < 0)2257 return setError(E_INVALIDARG, tr("The size argument (%lld) is negative"), aSize);2258 if (aSize == 0)2259 return setError(E_INVALIDARG, tr("The size (%lld) is zero"), aSize);2260 if (aFlags)2261 {2262 if (!(aFlags & ProcessOutputFlag_StdErr))2263 {2264 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);2265 }2266 }2267 2268 AutoCaller autoCaller(this);2269 if (FAILED(autoCaller.rc())) return autoCaller.rc();2270 2271 HRESULT rc = S_OK;2272 2273 try2274 {2275 VBOXGUESTCTRL_PROCESS proc;2276 int vrc = processGetStatus(aPID, &proc, false /* Don't remove */);2277 if (RT_FAILURE(vrc))2278 {2279 rc = setError(VBOX_E_IPRT_ERROR,2280 Guest::tr("Guest process (PID %u) does not exist"), aPID);2281 }2282 else if (proc.mStatus != ExecuteProcessStatus_Started)2283 {2284 /* If the process is already or still in the process table but does not run yet2285 * (or anymore) don't remove it but report back an appropriate error. */2286 vrc = VERR_BROKEN_PIPE;2287 /* Not getting any output is fine, so don't report an API error (rc)2288 * and only signal something through internal error code (vrc). */2289 }2290 2291 if (RT_SUCCESS(vrc))2292 {2293 uint32_t uContextID = 0;2294 2295 /*2296 * Create progress object.2297 * This progress object, compared to the one in executeProgress() above,2298 * is only single-stage local and is used to determine whether the operation2299 * finished or got canceled.2300 */2301 ComObjPtr <Progress> pProgress;2302 rc = pProgress.createObject();2303 if (SUCCEEDED(rc))2304 {2305 rc = pProgress->init(static_cast<IGuest*>(this),2306 Bstr(tr("Getting output for guest process")).raw(),2307 TRUE /* Cancelable */);2308 }2309 if (FAILED(rc)) throw rc;2310 ComAssert(!pProgress.isNull());2311 2312 /* Adjust timeout. */2313 if (aTimeoutMS == 0)2314 aTimeoutMS = UINT32_MAX;2315 2316 /* Set handle ID. */2317 uint32_t uHandleID = OUTPUT_HANDLE_ID_STDOUT; /* Default */2318 if (aFlags & ProcessOutputFlag_StdErr)2319 uHandleID = OUTPUT_HANDLE_ID_STDERR;2320 2321 uint32_t uGuestPID = processGetGuestPID(aPID);2322 Assert(uGuestPID);2323 2324 /** @todo Use a buffer for next iteration if returned data is too big2325 * for current read.2326 * aSize is bogus -- will be ignored atm! */2327 VBOXGUESTCTRL_CALLBACK callback;2328 vrc = callbackInit(&callback, VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT, pProgress);2329 if (RT_SUCCESS(vrc))2330 {2331 PCALLBACKDATAEXECOUT pData = (PCALLBACKDATAEXECOUT)callback.pvData;2332 2333 /* Save PID + output flags for later use. */2334 pData->u32PID = uGuestPID;2335 pData->u32Flags = aFlags;2336 }2337 2338 if (RT_SUCCESS(vrc))2339 vrc = callbackAdd(&callback, &uContextID);2340 2341 if (RT_SUCCESS(vrc))2342 {2343 VBOXHGCMSVCPARM paParms[5];2344 int i = 0;2345 paParms[i++].setUInt32(uContextID);2346 paParms[i++].setUInt32(uGuestPID);2347 paParms[i++].setUInt32(uHandleID);2348 paParms[i++].setUInt32(0 /* Flags, none set yet */);2349 2350 VMMDev *pVMMDev = NULL;2351 {2352 /* Make sure mParent is valid, so set the read lock while using.2353 * Do not keep this lock while doing the actual call, because in the meanwhile2354 * another thread could request a write lock which would be a bad idea ... */2355 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);2356 2357 /* Forward the information to the VMM device. */2358 AssertPtr(mParent);2359 pVMMDev = mParent->getVMMDev();2360 }2361 2362 if (pVMMDev)2363 {2364 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));2365 vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_GET_OUTPUT,2366 i, paParms);2367 }2368 }2369 2370 if (RT_SUCCESS(vrc))2371 {2372 LogFlowFunc(("Waiting for HGCM callback (timeout=%RI32ms) ...\n", aTimeoutMS));2373 2374 /*2375 * Wait for the HGCM low level callback until the process2376 * has been started (or something went wrong). This is necessary to2377 * get the PID.2378 */2379 2380 /*2381 * Wait for the first output callback notification to arrive.2382 */2383 vrc = callbackWaitForCompletion(uContextID, -1 /* No staging required */, aTimeoutMS);2384 if (RT_SUCCESS(vrc))2385 {2386 PCALLBACKDATAEXECOUT pExecOut = NULL;2387 vrc = callbackGetUserData(uContextID, NULL /* We know the type. */,2388 (void**)&pExecOut, NULL /* Don't need the size. */);2389 if (RT_SUCCESS(vrc))2390 {2391 com::SafeArray<BYTE> outputData((size_t)aSize);2392 2393 if (pExecOut->cbData)2394 {2395 bool fResize;2396 2397 /* Do we need to resize the array? */2398 if (pExecOut->cbData > aSize)2399 {2400 fResize = outputData.resize(pExecOut->cbData);2401 Assert(fResize);2402 }2403 2404 /* Fill output in supplied out buffer. */2405 Assert(outputData.size() >= pExecOut->cbData);2406 memcpy(outputData.raw(), pExecOut->pvData, pExecOut->cbData);2407 fResize = outputData.resize(pExecOut->cbData); /* Shrink to fit actual buffer size. */2408 Assert(fResize);2409 }2410 else2411 {2412 /* No data within specified timeout available. */2413 bool fResize = outputData.resize(0);2414 Assert(fResize);2415 }2416 2417 /* Detach output buffer to output argument. */2418 outputData.detachTo(ComSafeArrayOutArg(aData));2419 2420 callbackFreeUserData(pExecOut);2421 }2422 else2423 {2424 rc = setErrorNoLog(VBOX_E_IPRT_ERROR,2425 tr("Unable to retrieve process output data (%Rrc)"), vrc);2426 }2427 }2428 else2429 rc = setErrorCompletion(vrc);2430 }2431 else2432 rc = setErrorHGCM(vrc);2433 2434 {2435 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);2436 2437 /* The callback isn't needed anymore -- just was kept locally. */2438 callbackRemove(uContextID);2439 }2440 2441 /* Cleanup. */2442 if (!pProgress.isNull())2443 pProgress->uninit();2444 pProgress.setNull();2445 }2446 2447 if (pRC)2448 *pRC = vrc;2449 }2450 catch (std::bad_alloc &)2451 {2452 rc = E_OUTOFMEMORY;2453 if (pRC)2454 *pRC = VERR_NO_MEMORY;2455 }2456 return rc;2457 #endif2458 }2459 2460 STDMETHODIMP Guest::GetProcessStatus(ULONG aPID, ULONG *aExitCode, ULONG *aFlags, ExecuteProcessStatus_T *aStatus)2461 {2462 #ifndef VBOX_WITH_GUEST_CONTROL2463 ReturnComNotImplemented();2464 #else /* VBOX_WITH_GUEST_CONTROL */2465 2466 AutoCaller autoCaller(this);2467 if (FAILED(autoCaller.rc())) return autoCaller.rc();2468 2469 HRESULT rc = S_OK;2470 2471 try2472 {2473 VBOXGUESTCTRL_PROCESS process;2474 int vrc = processGetStatus(aPID, &process,2475 true /* Remove when terminated */);2476 if (RT_SUCCESS(vrc))2477 {2478 if (aExitCode)2479 *aExitCode = process.mExitCode;2480 if (aFlags)2481 *aFlags = process.mFlags;2482 if (aStatus)2483 *aStatus = process.mStatus;2484 }2485 else2486 rc = setError(VBOX_E_IPRT_ERROR,2487 tr("Process (PID %u) not found!"), aPID);2488 }2489 catch (std::bad_alloc &)2490 {2491 rc = E_OUTOFMEMORY;2492 }2493 return rc;2494 #endif2495 }2496 2497 STDMETHODIMP Guest::CopyFromGuest(IN_BSTR aSource, IN_BSTR aDest,2498 IN_BSTR aUsername, IN_BSTR aPassword,2499 ULONG aFlags, IProgress **aProgress)2500 {2501 #ifndef VBOX_WITH_GUEST_CONTROL2502 ReturnComNotImplemented();2503 #else /* VBOX_WITH_GUEST_CONTROL */2504 CheckComArgStrNotEmptyOrNull(aSource);2505 CheckComArgStrNotEmptyOrNull(aDest);2506 CheckComArgOutPointerValid(aProgress);2507 2508 /* Do not allow anonymous executions (with system rights). */2509 if (RT_UNLIKELY((aUsername) == NULL || *(aUsername) == '\0'))2510 return setError(E_INVALIDARG, tr("No user name specified"));2511 2512 AutoCaller autoCaller(this);2513 if (FAILED(autoCaller.rc())) return autoCaller.rc();2514 2515 /* Validate flags. */2516 if (aFlags != CopyFileFlag_None)2517 {2518 if ( !(aFlags & CopyFileFlag_Recursive)2519 && !(aFlags & CopyFileFlag_Update)2520 && !(aFlags & CopyFileFlag_FollowLinks))2521 {2522 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);2523 }2524 }2525 2526 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);2527 2528 HRESULT rc = S_OK;2529 2530 ComObjPtr<Progress> progress;2531 try2532 {2533 /* Create the progress object. */2534 progress.createObject();2535 2536 rc = progress->init(static_cast<IGuest*>(this),2537 Bstr(tr("Copying file from guest to host")).raw(),2538 TRUE /* aCancelable */);2539 if (FAILED(rc)) throw rc;2540 2541 /* Initialize our worker task. */2542 GuestTask *pTask = new GuestTask(GuestTask::TaskType_CopyFileFromGuest, this, progress);2543 AssertPtr(pTask);2544 std::auto_ptr<GuestTask> task(pTask);2545 2546 /* Assign data - aSource is the source file on the host,2547 * aDest reflects the full path on the guest. */2548 task->strSource = (Utf8Str(aSource));2549 task->strDest = (Utf8Str(aDest));2550 task->strUserName = (Utf8Str(aUsername));2551 task->strPassword = (Utf8Str(aPassword));2552 task->uFlags = aFlags;2553 2554 rc = task->startThread();2555 if (FAILED(rc)) throw rc;2556 2557 /* Don't destruct on success. */2558 task.release();2559 }2560 catch (HRESULT aRC)2561 {2562 rc = aRC;2563 }2564 2565 if (SUCCEEDED(rc))2566 {2567 /* Return progress to the caller. */2568 progress.queryInterfaceTo(aProgress);2569 }2570 return rc;2571 #endif /* VBOX_WITH_GUEST_CONTROL */2572 }2573 2574 STDMETHODIMP Guest::CopyToGuest(IN_BSTR aSource, IN_BSTR aDest,2575 IN_BSTR aUsername, IN_BSTR aPassword,2576 ULONG aFlags, IProgress **aProgress)2577 {2578 #ifndef VBOX_WITH_GUEST_CONTROL2579 ReturnComNotImplemented();2580 #else /* VBOX_WITH_GUEST_CONTROL */2581 CheckComArgStrNotEmptyOrNull(aSource);2582 CheckComArgStrNotEmptyOrNull(aDest);2583 CheckComArgOutPointerValid(aProgress);2584 2585 /* Do not allow anonymous executions (with system rights). */2586 if (RT_UNLIKELY((aUsername) == NULL || *(aUsername) == '\0'))2587 return setError(E_INVALIDARG, tr("No user name specified"));2588 2589 AutoCaller autoCaller(this);2590 if (FAILED(autoCaller.rc())) return autoCaller.rc();2591 2592 /* Validate flags. */2593 if (aFlags != CopyFileFlag_None)2594 {2595 if ( !(aFlags & CopyFileFlag_Recursive)2596 && !(aFlags & CopyFileFlag_Update)2597 && !(aFlags & CopyFileFlag_FollowLinks))2598 {2599 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);2600 }2601 }2602 2603 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);2604 2605 HRESULT rc = S_OK;2606 2607 ComObjPtr<Progress> pProgress;2608 try2609 {2610 /* Create the progress object. */2611 pProgress.createObject();2612 2613 rc = pProgress->init(static_cast<IGuest*>(this),2614 Bstr(tr("Copying file from host to guest")).raw(),2615 TRUE /* aCancelable */);2616 if (FAILED(rc)) throw rc;2617 2618 /* Initialize our worker task. */2619 GuestTask *pTask = new GuestTask(GuestTask::TaskType_CopyFileToGuest, this, pProgress);2620 AssertPtr(pTask);2621 std::auto_ptr<GuestTask> task(pTask);2622 2623 /* Assign data - aSource is the source file on the host,2624 * aDest reflects the full path on the guest. */2625 task->strSource = (Utf8Str(aSource));2626 task->strDest = (Utf8Str(aDest));2627 task->strUserName = (Utf8Str(aUsername));2628 task->strPassword = (Utf8Str(aPassword));2629 task->uFlags = aFlags;2630 2631 rc = task->startThread();2632 if (FAILED(rc)) throw rc;2633 2634 /* Don't destruct on success. */2635 task.release();2636 }2637 catch (HRESULT aRC)2638 {2639 rc = aRC;2640 }2641 2642 if (SUCCEEDED(rc))2643 {2644 /* Return progress to the caller. */2645 pProgress.queryInterfaceTo(aProgress);2646 }2647 return rc;2648 #endif /* VBOX_WITH_GUEST_CONTROL */2649 }2650 131 2651 132 STDMETHODIMP Guest::UpdateGuestAdditions(IN_BSTR aSource, ComSafeArrayIn(AdditionsUpdateFlag_T, aFlags), IProgress **aProgress) … … 2676 157 2677 158 HRESULT hr = S_OK; 2678 #if 1 159 2679 160 /* Create an anonymous session. This is required to run the Guest Additions 2680 161 * update process with administrative rights. */ … … 2724 205 } 2725 206 return hr; 2726 #else /* Legacy, can be removed later. */2727 ComObjPtr<Progress> progress;2728 try2729 {2730 /* Create the progress object. */2731 progress.createObject();2732 2733 rc = progress->init(static_cast<IGuest*>(this),2734 Bstr(tr("Updating Guest Additions")).raw(),2735 TRUE /* aCancelable */);2736 if (FAILED(rc)) throw rc;2737 2738 /* Initialize our worker task. */2739 GuestTask *pTask = new GuestTask(GuestTask::TaskType_UpdateGuestAdditions, this, progress);2740 AssertPtr(pTask);2741 std::auto_ptr<GuestTask> task(pTask);2742 2743 /* Assign data - in that case aSource is the full path2744 * to the Guest Additions .ISO we want to mount. */2745 task->strSource = (Utf8Str(aSource));2746 task->uFlags = aFlags;2747 2748 rc = task->startThread();2749 if (FAILED(rc)) throw rc;2750 2751 /* Don't destruct on success. */2752 task.release();2753 }2754 catch (HRESULT aRC)2755 {2756 rc = aRC;2757 }2758 2759 if (SUCCEEDED(rc))2760 {2761 /* Return progress to the caller. */2762 progress.queryInterfaceTo(aProgress);2763 }2764 return rc;2765 #endif2766 207 #endif /* VBOX_WITH_GUEST_CONTROL */ 2767 208 } -
trunk/src/VBox/Main/src-client/GuestImpl.cpp
r42817 r42864 32 32 33 33 #include <VBox/VMMDev.h> 34 #ifdef VBOX_WITH_GUEST_CONTROL35 # include <VBox/com/array.h>36 # include <VBox/com/ErrorInfo.h>37 #endif38 34 #include <iprt/cpp/utils.h> 39 35 #include <iprt/timer.h> … … 108 104 AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc)); 109 105 110 #ifdef VBOX_WITH_GUEST_CONTROL111 /* Init the context ID counter at 1000. */112 mNextContextID = 1000;113 /* Init the host PID counter. */114 mNextHostPID = 0;115 #endif116 117 106 #ifdef VBOX_WITH_DRAG_AND_DROP 118 107 m_pGuestDnD = new GuestDnD(this); … … 134 123 if (autoUninitSpan.uninitDone()) 135 124 return; 136 137 #ifdef VBOX_WITH_GUEST_CONTROL138 /* Scope write lock as much as possible. */139 {140 /*141 * Cleanup must be done *before* AutoUninitSpan to cancel all142 * all outstanding waits in API functions (which hold AutoCaller143 * ref counts).144 */145 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);146 147 /* Notify left over callbacks that we are about to shutdown ... */148 CallbackMapIter it;149 for (it = mCallbackMap.begin(); it != mCallbackMap.end(); it++)150 {151 int rc2 = callbackNotifyEx(it->first, VERR_CANCELLED,152 Guest::tr("VM is shutting down, canceling uncompleted guest requests ..."));153 AssertRC(rc2);154 }155 156 /* Destroy left over callback data. */157 for (it = mCallbackMap.begin(); it != mCallbackMap.end(); it++)158 callbackDestroy(it->first);159 160 /* Clear process map (remove all callbacks). */161 mGuestProcessMap.clear();162 }163 #endif164 125 165 126 /* Destroy stat update timer */ -
trunk/src/VBox/Main/src-client/xpcom/module.cpp
r42852 r42864 67 67 NS_DECL_CLASSINFO(GuestDirectory) 68 68 NS_IMPL_THREADSAFE_ISUPPORTS2_CI(GuestDirectory, IGuestDirectory, IDirectory) 69 NS_DECL_CLASSINFO(GuestDirEntry)70 NS_IMPL_THREADSAFE_ISUPPORTS1_CI(GuestDirEntry, IGuestDirEntry)71 69 NS_DECL_CLASSINFO(GuestFile) 72 70 NS_IMPL_THREADSAFE_ISUPPORTS2_CI(GuestFile, IGuestFile, IFile)
Note:
See TracChangeset
for help on using the changeset viewer.