GuestCtrlImpl.cpp revision 25771ba4fe7e9fb9be963cf8289df6a0e8f93e56
// public methods only for internal purposes ///////////////////////////////////////////////////////////////////////////// * Appends environment variables to the environment block. * Each var=value pair is separated by the null character ('\\0'). The whole * block will be stored in one blob and disassembled on the guest side later to * fit into the HGCM param structure. * @returns VBox status code. * @param pszEnvVar The environment variable=value to append to the * @param ppvList This is actually a pointer to a char pointer * variable which keeps track of the environment block * that we're constructing. * @param pcbList Pointer to the variable holding the current size of * the environment block. (List is a misnomer, go * @param pcEnvVars Pointer to the variable holding count of variables * stored in the environment block. *
pcEnvVars +=
1;
/* Increase env variable count. */ * Adds a callback with a user provided data block and an optional progress object * to the callback map. A callback is identified by a unique context ID which is used * to identify a callback from the guest side. * @return IPRT status code. /* puContextID is optional. */ /* Create a new context ID and assign it. */ /* Create a new context ID ... */ /* Is the context ID already used? Try next ID ... */ /* Callback with context ID was not found. This means * we can use this context ID for our new callback we want break;
/* Don't try too hard. */ /* Add callback with new context ID to our callback map. */ /* Report back new context ID. */ * Assigns a host PID to a specified context. * Destroys the formerly allocated callback data. The callback then * needs to get removed from the callback map via callbackRemove(). * Removes a callback from the callback map. * Checks whether a callback with the given context ID * @return bool True, if callback exists, false if not. * @param uContextID Context ID to check. * Frees the user data (actual context data) of a callback. * @param pvData Data to free. * Retrieves the (generated) host PID of a given callback. * @return The host PID, if found, 0 otherwise. * @param uContextID Context ID to lookup host PID for. * @param puHostPID Where to store the host PID. /* pEnmType is optional. */ /* ppvData is optional. */ /* pcbData is optional. */ /* Does not do locking! Caller has to take care of it because the caller needs to /* uContextID can be 0. */ /* pcbData is optional. */ /* Everything else is optional. */ return true;
/* If no context ID given then take a shortcut. */ return true;
/* No progress / error means canceled. */ * Notifies a specified callback about its final status. * @return IPRT status code. LogFlowFunc((
"Checking whether callback (CID=%u) needs notification iRC=%Rrc, pszMsg=%s\n",
/* If progress already canceled do nothing here. */ * Assume we didn't complete to make sure we clean up even if the LogFlowFunc((
"Notifying callback with CID=%u, iRC=%Rrc, pszMsg=%s\n",
* To get waitForCompletion completed (unblocked) we have to notify it if necessary (only * cancel won't work!). This could happen if the client thread (e.g. VBoxService, thread of a spawned process) * is disconnecting without having the chance to sending a status message before, so we * have to abort here to make sure the host never hangs/gets stuck while waiting for the * progress object to become signalled. LogFlowFunc((
"Notified callback with CID=%u returned %Rhrc (0x%x)\n",
* Do *not* NULL pProgress here, because waiting function like executeProcess() * will still rely on this object for checking whether they have to give up! /* If pProgress is not found (anymore) that's fine. * Might be destroyed already. */ * Notifies all callbacks which are assigned to a certain guest PID to set a certain * @return IPRT status code. * @param uGuestPID Guest PID to find all callbacks for. * @param iRC Return (error) code to set for the found callbacks. * @param pszMessage Optional (error) message to set. /* When waiting for process output while the process is destroyed, * make sure we also destroy the actual waiting operation (internal progress object) * in order to not block the caller. */ /* When waiting for injecting process input while the process is destroyed, * make sure we also destroy the actual waiting operation (internal progress object) * in order to not block the caller. */ * Waits for a callback (using its context ID) to complete. * @return IPRT status code. * @param uContextID Context ID to wait for. * @param lStage Stage to wait for. Specify -1 if no staging is present/required. * Specifying a stage is only needed if there's a multi operation progress * @param lTimeout Timeout (in ms) to wait for. * Wait for the HGCM low level callback until the process * has been started (or something went wrong). This is necessary to LogFlowFunc((
"Waiting for callback completion (CID=%u, Stage=%RI32, timeout=%RI32ms) ...\n",
LogFlowFunc((
"Callback (CID=%u) completed with rc=%Rrc\n",
* Static callback function for receiving updates on guest control commands * from the guest. Acts as a dispatcher for the actual class instance. * @returns VBox status code. * No locking, as this is purely a notification which does not make any * changes to the object state. LogFlowFunc((
"pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
* For guest control 2.0 using the legacy commands we need to do the following here: * - Get the callback header to access the context ID * - Get the context ID of the callback * - Extract the session ID out of the context ID * - Dispatch the whole stuff to the appropriate session (if still exists) LogFlowFunc((
"CID=%RU32, uSession=%RU32, uProcess=%RU32, uCount=%RU32\n",
* Pre-check: If we got a status message with an error and VERR_TOO_MUCH_DATA * it means that that guest could not handle the entire message * because of its exceeding size. This should not happen on daily * use but testcases might try this. It then makes no sense to dispatch * this further because we don't have a valid context ID. LogFlowFunc((
"Requested command with too much data, skipping dispatching ...\n"));
/* @todo Don't use legacy stuff in debug mode. Remove for final! */ /* Silently ignore not implemented functions. */ /* Callback can be called several times. */ /* The context ID might be 0 in case the guest was not able to fetch * actual command. So we can't do much now but report an error. */ /* Scope write locks as much as possible. */ LogFlowFunc((
"Execution status (CID=%u, pData=0x%p)\n",
/** @todo Copy void* buffer contents? */ /* If pCallbackData is NULL this might be an old request for which no user data * might exist anymore. */ /* Was progress canceled before? */ /* Handle process map. This needs to be done first in order to have a valid * map in case some callback gets notified a bit below. */ /* Note: PIDs never get removed here in case the guest process signalled its * end; instead the next call of GetProcessStatus() will remove the PID * from the process map after we got the process' final (exit) status. * See waitpid() for an example. */ /* Just reach through flags. */ /* Interprete u32Flags as the guest process' exit code. */ /* Do progress handling. */ LogRel((
"Guest process (PID %u) started\n",
pData->
u32PID));
/** @todo Add process name */ LogRel((
"Guest process (PID %u) exited normally (exit code: %u)\n",
LogRel((
"Guest process (PID %u) terminated abnormally (exit code: %u)\n",
LogRel((
"Guest process (PID %u) terminated through signal (exit code: %u)\n",
LogRel((
"Guest process (PID %u) timed out and was killed\n",
pData->
u32PID));
/** @todo Add process name */ LogRel((
"Guest process (PID %u) timed out and could not be killed\n",
pData->
u32PID));
/** @todo Add process name */ LogRel((
"Guest process (PID %u) killed because system is shutting down\n",
pData->
u32PID));
/** @todo Add process name */ * If u32Flags has ExecuteProcessFlag_IgnoreOrphanedProcesses set, we don't report an error to * our progress object. This is helpful for waiters which rely on the success of our progress object * even if the executed process was killed because the system/VBoxService is shutting down. * In this case u32Flags contains the actual execution flags reached in via Guest::ExecuteProcess(). switch (
pData->
u32Flags)
/* u32Flags member contains the IPRT error code from guest side. */ errDetail =
Utf8StrFmt(
Guest::
tr(
"Guest process could not be started because maximum number of parallel guest processes has been reached"));
/* Do we need to handle the callback error? */ /* Notify all callbacks which are still waiting on something * which is related to the current PID. */ LogFlowFunc((
"Failed to notify other callbacks for PID=%u\n",
/* Let the caller know what went wrong ... */ LogFlowFunc((
"Failed to notify callback CID=%u for PID=%u\n",
/* Since we don't know which context exactly failed all we can do is to shutdown /* Cancel all callbacks. */ LogFlowFunc((
"Process (CID=%u, status=%u) reported: %s\n",
LogFlowFunc((
"Returned with rc=%Rrc, rcCallback=%Rrc\n",
/* Function for handling the execution output notification. */ /* Scope write locks as much as possible. */ /* Make sure we really got something! */ /* Allocate data buffer and copy it */ else /* Nothing received ... */ /* If pCallbackData is NULL this might be an old request for which no user data * might exist anymore. */ Guest::
tr(
"The output operation was canceled"));
/* Function for handling the execution input status notification. */ /* Scope write locks as much as possible. */ /* Save bytes processed. */ /* If pCallbackData is NULL this might be an old request for which no user data * might exist anymore. */ * Gets guest process information. Removes the process from the map * @return IPRT status code. * @param uHostPID Host PID of guest process to get status for. * @param pProcess Where to store the process information. Optional. * @param fRemove Flag indicating whether to remove the * process from the map when process marked a /* Only remove processes from our map when they signalled their final * Sets the current status of a guest process. * @return IPRT status code. * @param uHostPID Host PID of guest process to set status (and guest PID) for. * @param uGuestPID Guest PID to assign to the host PID. * @param enmStatus Current status to set. * @param uExitCode Exit code (if any). * @param uFlags Additional flags. /* Assigning a guest PID is optional. */ /* uGuestPID is optional -- the caller could call this function * before the guest process actually was started and a (valid) guest PID tr(
"VMM device is not available (is the VM running?)"));
tr(
"Process execution has been canceled"));
tr(
"The guest did not respond within time"));
tr(
"Waiting for completion failed with error %Rrc"),
rc);
:
Utf8StrFmt(
Guest::
tr(
"Process neither completed nor canceled; this shouldn't happen"));
tr(
"VMM device is not available (is the VM running?)"));
tr(
"The guest execution service is not ready (yet)"));
tr(
"The guest execution service is not available"));
else /* HGCM call went wrong. */ tr(
"The HGCM call failed with error %Rrc"),
rc);
#
endif /* VBOX_WITH_GUEST_CONTROL *//** @todo r=bird: Eventually we should clean up all the timeout parameters * in the API and have the same way of specifying infinite waits! */ #
else /* VBOX_WITH_GUEST_CONTROL */ /* Do not allow anonymous executions (with system rights). */ LogRel((
"Executing guest process \"%s\" as user \"%s\" ...\n",
* Executes and waits for an internal tool (that is, a tool which is integrated into * VBoxService, beginning with "vbox_" (e.g. "vbox_ls")) to finish its operation. * @param aTool Name of tool to execute. * @param aDescription Friendly description of the operation. * @param aFlags Execution flags. * @param aUsername Username to execute tool under. * @param aPassword The user's password. * @param uFlagsToAdd ExecuteProcessFlag flags to add to the execution operation. * @param aProgress Pointer which receives the tool's progress object. Optional. * @param aPID Pointer which receives the tool's PID. Optional. /* Wait for tool being started. */ /* Return the progress to the caller. */ /** @todo Add more stuff! */ * Tries to drain the guest's output and fill it into * a guest process stream object for later usage. * @todo What's about specifying stderr? * @return IPRT status code. * @param aPID PID of process to get the output from. * @param aFlags Which stream to drain (stdout or stderr). * @param pStream Pointer to guest process stream to fill. If NULL, /* pStream is optional. */ 0
/* Infinite timeout */,
continue;
/* Try one more time. */ else /* No more output and/or error! */ * Tries to retrieve the next stream block of a given stream and * drains the process output only as much as needed to get this next * @return IPRT status code. LogFlowFunc((
"Getting next stream block of PID=%u, Flags=%u; cbStrmSize=%u, cbStrmOff=%u\n",
LogFlowFunc((
"Parsing block rc=%Rrc, strmBlockCnt=%ld\n",
0
/* Infinite timeout */,
/* Reset found pairs to not break out too early and let all the new * data to be parsed as well. */ continue;
/* Try one more time. */ /* No more output to drain from stream. */ /* We haved drained the stream as much as we can and reached the * end of our stream buffer -- that means that there simply is no * stream block anymore, which is ok. */ LogFlowFunc((
"Returned with strmBlockCnt=%ld, cPairs=%ld, rc=%Rrc\n",
* Tries to get the next upcoming value block from a started guest process * by first draining its output and then processing the received guest stream. * @return IPRT status code. * @param ulPID PID of process to get/parse the output from. * @param stream Reference to process stream object to use. * @param streamBlock Reference that receives the next stream block data. * Gets output from a formerly started guest process, tries to parse all of its guest * stream (as long as data is available) and returns a stream object which can contain * multiple stream blocks (which in turn then can contain key=value pairs). * @param ulPID PID of process to get/parse the output from. * @param streamObjects Reference to a guest stream object structure for * storing the parsed data. /* Try to parse the stream output we gathered until now. If we still need more * data the parsing routine will tell us and we just do another poll round. */ tr(
"Error while parsing guest output (%Rrc)"),
rc);
* Waits for a fomerly started guest process to exit using its progress * object and returns its final status. Returns E_ABORT if guest process * @return IPRT status code. * @param uPID PID of guest process to wait for. * @param pProgress Progress object to wait for. * @param uTimeoutMS Timeout (in ms) for waiting; use 0 for * @param pRetStatus Pointer where to store the final process * @param puRetExitCode Pointer where to store the final process tr(
"Waiting for guest process to end failed (%Rhrc)"),
rc);
* Does the actual guest process execution, internal function. * @param aCommand Command line to execute. * @param aFlags Execution flags. * @param Username Username to execute the process with. * @param aPassword The user's password. * @param aTimeoutMS Timeout (in ms) to wait for the execution operation. * @param aPID Pointer that receives the guest process' PID. * @param aProgress Pointer that receives the guest process' progress object. * @param pRC Pointer that receives the internal IPRT return code. Optional. /** @todo r=bird: Eventually we should clean up all the timeout parameters * in the API and have the same way of specifying infinite waits! */ * Create progress object. Note that this is a multi operation * object to perform the following steps: * - Operation 2 (1): Wait for process to exit. * If this progress completed successfully (S_OK), the process 2,
/* Number of operations. */ Bstr(
tr(
"Starting process ...")).
raw());
/* Description of first stage. */ * Prepare process execution. /* Adjust timeout. If set to 0, we define * an infinite timeout. */ /* Prepare environment. */ for (
unsigned i = 0; i <
env.
size(); i++)
* If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout * until the process was started - the process itself then gets an infinite timeout for execution. * This is handy when we want to start a process inside a worker thread within a certain timeout * but let the started process perform lengthly operations then. /* Make sure mParent is valid, so set the read lock while using. * Do not keep this lock while doing the actual call, because in the meanwhile * another thread could request a write lock which would be a bad idea ... */ /* Forward the information to the VMM device. */ * Generate a host-driven PID so that we immediately can return to the caller and * don't need to wait until the guest started the desired process to return the * PID generated by the guest OS. * The guest PID will later be mapped to the host PID for later lookup. /* Create a new context ID ... */ /* Is the host PID already used? Try next PID ... */ /* Host PID not used (anymore), we're done here ... */ break;
/* Don't try too hard. */ 0
/* uExitCode */, 0
/* uFlags */);
/* Return the progress to the caller. */ if (!
pRC)
/* Skip logging internal calls. */ LogRel((
"Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",
#
endif /* VBOX_WITH_GUEST_CONTROL */#
else /* VBOX_WITH_GUEST_CONTROL */ /* PID exists; check if process is still running. */ Guest::
tr(
"Cannot inject input to a not running process (PID %u)"),
aPID);
Guest::
tr(
"Cannot inject input to a non-existent process (PID %u)"),
aPID);
* Create progress object. * This progress object, compared to the one in executeProgress() above, * is only single-stage local and is used to determine whether the operation * finished or got canceled. /* Save PID + output flags for later use. */ /* Make sure mParent is valid, so set the read lock while using. * Do not keep this lock while doing the actual call, because in the meanwhile * another thread could request a write lock which would be a bad idea ... */ /* Forward the information to the VMM device. */ * Wait for getting back the input response from the guest. tr(
"Client reported error %Rrc while processing input data"),
tr(
"Client terminated while processing input data"));
tr(
"Client reported buffer overflow while processing input data"));
/*AssertReleaseMsgFailed(("Client reported unknown input error, status=%u, flags=%u\n", pExecStatusIn->u32Status, pExecStatusIn->u32Flags));*/ tr(
"Unable to retrieve process input status data"));
/* The callback isn't needed anymore -- just was kept locally. */ #
else /* VBOX_WITH_GUEST_CONTROL *//** @todo r=bird: Eventually we should clean up all the timeout parameters * in the API and have the same way of specifying infinite waits! */ #
else /* VBOX_WITH_GUEST_CONTROL */ Guest::
tr(
"Guest process (PID %u) does not exist"),
aPID);
/* If the process is already or still in the process table but does not run yet * (or anymore) don't remove it but report back an appropriate error. */ /* Not getting any output is fine, so don't report an API error (rc) * and only signal something through internal error code (vrc). */ * Create progress object. * This progress object, compared to the one in executeProgress() above, * is only single-stage local and is used to determine whether the operation * finished or got canceled. Bstr(
tr(
"Getting output for guest process")).
raw(),
/** @todo Use a buffer for next iteration if returned data is too big * aSize is bogus -- will be ignored atm! */ /* Save PID + output flags for later use. */ /* Make sure mParent is valid, so set the read lock while using. * Do not keep this lock while doing the actual call, because in the meanwhile * another thread could request a write lock which would be a bad idea ... */ /* Forward the information to the VMM device. */ * Wait for the HGCM low level callback until the process * has been started (or something went wrong). This is necessary to * Wait for the first output callback notification to arrive. /* Do we need to resize the array? */ /* Fill output in supplied out buffer. */ /* No data within specified timeout available. */ /* Detach output buffer to output argument. */ tr(
"Unable to retrieve process output data (%Rrc)"),
vrc);
/* The callback isn't needed anymore -- just was kept locally. */ #
else /* VBOX_WITH_GUEST_CONTROL */ true /* Remove when terminated */);
tr(
"Process (PID %u) not found!"),
aPID);
#
else /* VBOX_WITH_GUEST_CONTROL */ /* Do not allow anonymous executions (with system rights). */ /* Create the progress object. */ Bstr(
tr(
"Copying file from guest to host")).
raw(),
/* Initialize our worker task. */ /* Assign data - aSource is the source file on the host, * aDest reflects the full path on the guest. */ /* Don't destruct on success. */ /* Return progress to the caller. */ #
endif /* VBOX_WITH_GUEST_CONTROL */#
else /* VBOX_WITH_GUEST_CONTROL */ /* Do not allow anonymous executions (with system rights). */ /* Create the progress object. */ Bstr(
tr(
"Copying file from host to guest")).
raw(),
/* Initialize our worker task. */ /* Assign data - aSource is the source file on the host, * aDest reflects the full path on the guest. */ /* Don't destruct on success. */ /* Return progress to the caller. */ #
endif /* VBOX_WITH_GUEST_CONTROL */#
else /* VBOX_WITH_GUEST_CONTROL */ /* Create an anonymous session. This is required to run the Guest Additions * update process with administrative rights. */ "Updating Guest Additions" /* Name */,
pSession);
/** @todo Add more errors here. */ /* Return progress to the caller. */ tr(
"Starting task for updating Guest Additions on the guest failed: %Rrc"),
rc);
#
else /* Legacy, can be removed later. */ /* Create the progress object. */ /* Initialize our worker task. */ /* Assign data - in that case aSource is the full path * to the Guest Additions .ISO we want to mount. */ /* Don't destruct on success. */ /* Return progress to the caller. */ #
endif /* VBOX_WITH_GUEST_CONTROL *////////////////////////////////////////////////////////////////////////////// /* Create a new session ID and assign it. */ /* Is the context ID already used? */ break;
/* Don't try too hard. */ /* Create the session object. */ LogFlowFunc((
"Added new session with session ID=%RU32 (now %ld sessions total)\n",
// implementation of public methods ///////////////////////////////////////////////////////////////////////////// #
else /* VBOX_WITH_GUEST_CONTROL */ /* Do not allow anonymous sessions (with system rights) with official API. */ /* Return guest session to the caller. */ /** @todo Add more errors here. */ #
endif /* VBOX_WITH_GUEST_CONTROL */#
else /* VBOX_WITH_GUEST_CONTROL */ tr(
"Could not find sessions with name '%ls'"),
#
endif /* VBOX_WITH_GUEST_CONTROL */