ConsoleImplTeleporter.cpp revision 7127f9b4666b840219e35f6a171f6bdc541a09d4
/* $Id$ */
/** @file
* VBox Console COM Class implementation, The Teleporter Part.
*/
/*
* Copyright (C) 2009 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "ConsoleImpl.h"
#include "Global.h"
#include "ProgressImpl.h"
#include "AutoCaller.h"
#include "Logging.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Base class for the teleporter state.
*
* These classes are used as advanced structs, not as proper classes.
*/
class TeleporterState
{
public:
bool const mfIsSource;
/** @name stream stuff
* @{ */
bool volatile mfStopReading;
bool volatile mfEndOfStream;
bool volatile mfIOError;
/** @} */
, mcbReadBlock(0)
, mfStopReading(false)
, mfEndOfStream(false)
, mfIOError(false)
{
}
};
/**
* Teleporter state used by the source side.
*/
class TeleporterStateSrc : public TeleporterState
{
public:
bool mfSuspendedByUs;
bool mfUnlockedMedia;
TeleporterStateSrc(Console *pConsole, PVM pVM, Progress *pProgress, MachineState_T enmOldMachineState)
, muPort(UINT32_MAX)
, mcMsMaxDowntime(250)
, mfSuspendedByUs(false)
, mfUnlockedMedia(false)
{
}
};
/**
* Teleporter state used by the destiation side.
*/
class TeleporterStateTrg : public TeleporterState
{
public:
bool mfLockedMedia;
int mRc;
, mfLockedMedia(false)
, mRc(VINF_SUCCESS)
{
}
};
/**
* TCP stream header.
*
* This is an extra layer for fixing the problem with figuring out when the SSM
* stream ends.
*/
typedef struct TELEPORTERTCPHDR
{
/** Magic value. */
/** The size of the data block following this header.
* 0 indicates the end of the stream, while UINT32_MAX indicates
* cancelation. */
/** Magic value for TELEPORTERTCPHDR::u32Magic. (Egberto Gismonti Amin) */
/** The max block size. */
/*******************************************************************************
* Global Variables *
*******************************************************************************/
static const char g_szWelcome[] = "VirtualBox-Teleporter-1.0\n";
/**
* Reads a string from the socket.
*
* @returns VBox status code.
*
* @param pState The teleporter state structure.
* @param pszBuf The output buffer.
* @param cchBuf The size of the output buffer.
*
*/
{
*pszBuf = '\0';
/* dead simple approach. */
for (;;)
{
char ch;
if (RT_FAILURE(rc))
{
return rc;
}
if ( ch == '\n'
|| ch == '\0')
return VINF_SUCCESS;
if (cchBuf <= 1)
{
return VERR_BUFFER_OVERFLOW;
}
*pszBuf = '\0';
cchBuf--;
}
}
/**
* Reads an ACK or NACK.
*
* @returns S_OK on ACK, E_FAIL+setError() on failure or NACK.
* @param pState The teleporter source state.
* @param pszWhich Which ACK is this this?
* @param pszNAckMsg Optional NACK message.
*
* @remarks the setError laziness forces this to be a Console member.
*/
const char *pszNAckMsg /*= NULL*/)
{
char szMsg[128];
if (RT_FAILURE(vrc))
{
{
if (vrc == VINF_SUCCESS)
{
if (pszNAckMsg)
{
}
}
}
}
return S_OK;
}
/**
* Submitts a command to the destination and waits for the ACK.
*
* @returns S_OK on ACKed command, E_FAIL+setError() on failure.
*
* @param pState The teleporter source state.
* @param pszCommand The command.
* @param fWaitForAck Whether to wait for the ACK.
*
* @remarks the setError laziness forces this to be a Console member.
*/
Console::teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck /*= true*/)
{
if (RT_SUCCESS(vrc))
if (RT_SUCCESS(vrc))
if (RT_FAILURE(vrc))
if (!fWaitForAck)
return S_OK;
}
/**
* @copydoc SSMSTRMOPS::pfnWrite
*/
static DECLCALLBACK(int) teleporterTcpOpWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
{
for (;;)
{
/* Write block header. */
if (RT_FAILURE(rc))
{
return rc;
}
/* Write the data. */
if (RT_FAILURE(rc))
{
return rc;
}
return VINF_SUCCESS;
/* advance */
}
}
/**
* Selects and poll for close condition.
*
* We can use a relatively high poll timeout here since it's only used to get
* us out of error paths. In the normal cause of events, we'll get a
* end-of-stream header.
*
* @returns VBox status code.
*
* @param pState The teleporter state data.
*/
{
int rc;
do
{
{
break;
}
if (pState->mfStopReading)
{
break;
}
} while (rc == VERR_TIMEOUT);
return rc;
}
/**
* @copydoc SSMSTRMOPS::pfnRead
*/
static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
{
for (;;)
{
int rc;
/*
* Check for various conditions and may have been signalled.
*/
if (pState->mfEndOfStream)
return VERR_EOF;
if (pState->mfStopReading)
return VERR_EOF;
return VERR_IO_GEN_FAILURE;
/*
* If there is no more data in the current block, read the next
* block header.
*/
if (!pState->mcbReadBlock)
{
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
{
return rc;
}
{
)
{
pState->mfEndOfStream = true;
pState->mcbReadBlock = 0;
}
return VERR_IO_GEN_FAILURE;
}
if (pState->mfStopReading)
return VERR_EOF;
}
/*
* Read more data.
*/
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
{
return rc;
}
if (pcbRead)
{
return VINF_SUCCESS;
}
return VINF_SUCCESS;
/* Advance to the next block. */
}
}
/**
* @copydoc SSMSTRMOPS::pfnSeek
*/
static DECLCALLBACK(int) teleporterTcpOpSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
{
return VERR_NOT_SUPPORTED;
}
/**
* @copydoc SSMSTRMOPS::pfnTell
*/
{
return pState->moffStream;
}
/**
* @copydoc SSMSTRMOPS::pfnSize
*/
{
return VERR_NOT_SUPPORTED;
}
/**
* @copydoc SSMSTRMOPS::pfnIsOk
*/
{
if (pState->mfIsSource)
{
/* Poll for incoming NACKs and errors from the other side */
if (rc != VERR_TIMEOUT)
{
if (RT_SUCCESS(rc))
{
LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n"));
}
else
return rc;
}
}
return VINF_SUCCESS;
}
/**
* @copydoc SSMSTRMOPS::pfnClose
*/
{
if (pState->mfIsSource)
{
if (RT_SUCCESS(rc))
if (RT_FAILURE(rc))
{
return rc;
}
}
else
{
}
return VINF_SUCCESS;
}
/**
* Method table for a TCP based stream.
*/
static SSMSTRMOPS const g_teleporterTcpOps =
{
};
/**
* Progress cancelation callback.
*/
static void teleporterProgressCancelCallback(void *pvUser)
{
if (!pState->mfIsSource)
{
}
}
/**
* @copydoc PFNVMPROGRESS
*/
{
if (pState->mptrProgress)
{
{
/* check if the failure was caused by cancellation. */
{
return VERR_SSM_CANCELLED;
}
}
}
return VINF_SUCCESS;
}
/**
* @copydoc FNRTTIMERLR
*/
{
/* This is harmless for any open connections. */
}
/**
* Do the teleporter.
*
* @returns VBox status code.
* @param pState The teleporter state.
*/
{
AutoCaller autoCaller(this);
/*
* Wait for Console::Teleport to change the state.
*/
return hrc;
if (fCancelled)
/*
* Try connect to the destination machine.
* (Note. The caller cleans up mhSocket, so we can return without worries.)
*/
if (RT_FAILURE(vrc))
/* Read and check the welcome message. */
if (RT_FAILURE(vrc))
/* password */
if (RT_FAILURE(vrc))
/* ACK */
return hrc;
/*
* Start loading the state.
*
* Note! The saved state includes vital configuration data which will be
* verified against the VM config on the other end. This is all done
* in the first pass, so we should fail pretty promptly on misconfig.
*/
return hrc;
if (RT_FAILURE(vrc))
return hrc;
/*
* We're at the point of no return.
*/
{
return E_FAIL;
}
/*
* Hand over any media which we might be sharing.
*
* Note! This is only important on localhost teleportations.
*/
/** @todo Maybe we should only do this if it's a local teleportation... */
return hrc;
pState->mfUnlockedMedia = true;
return hrc;
/*
* The FINAL step is giving the target instructions how to proceed with the VM.
*/
if ( vrc == VINF_SSM_LIVE_SUSPENDED
else
return hrc;
/*
* teleporterSrcThreadWrapper will do the automatic power off because it
* has to release the AutoVMCaller.
*/
return S_OK;
}
/**
* Static thread method wrapper.
*
* @returns VINF_SUCCESS (ignored).
* @param hThread The thread.
* @param pvUser Pointer to a TeleporterStateSrc instance.
*/
/*static*/ DECLCALLBACK(int)
{
/*
* Console::teleporterSrc does the work, we just grab onto the VM handle
* and do the cleanups afterwards.
*/
/* Close the connection ASAP on so that the other side can complete. */
{
}
/* Aaarg! setMachineState trashes error info on Windows, so we have to
complete things here on failure instead of right before cleanup. */
/* We can no longer be cancelled (success), or it doesn't matter any longer (failure). */
/*
* Write lock the console before resetting mptrCancelableProgress and
* fixing the state.
*/
{
/*
* Automatically shut down the VM on success.
*
* Note! We have to release the VM caller object or we'll deadlock in
* powerDown.
*/
AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, ("%s\n", Global::stringifyMachineState(enmMachineState)));
pState->mptrConsole->mVMIsAlreadyPoweringOff = true; /* (Make sure we stick in the TeleportingPausedVM state.) */
}
else
{
/*
* Work the state machinery on failure.
*
* If the state is no longer 'Teleporting*', some other operation has
* canceled us and there is nothing we need to do here. In all other
* cases, we've failed one way or another.
*/
)
{
if (pState->mfUnlockedMedia)
{
{
do
{
RTThreadSleep(2);
}
pState->mfUnlockedMedia = true;
else
}
switch (enmVMState)
{
case VMSTATE_RUNNING:
case VMSTATE_RUNNING_LS:
case VMSTATE_DEBUGGING:
case VMSTATE_DEBUGGING_LS:
case VMSTATE_POWERING_OFF:
case VMSTATE_POWERING_OFF_LS:
case VMSTATE_RESETTING:
case VMSTATE_RESETTING_LS:
break;
case VMSTATE_GURU_MEDITATION:
break;
case VMSTATE_FATAL_ERROR:
case VMSTATE_FATAL_ERROR_LS:
break;
default:
case VMSTATE_SUSPENDED:
case VMSTATE_SUSPENDED_LS:
case VMSTATE_SUSPENDING:
case VMSTATE_SUSPENDING_LS:
if (!pState->mfUnlockedMedia)
{
if (pState->mfSuspendedByUs)
{
}
}
else
{
/* Faking a guru meditation is the best I can think of doing here... */
}
break;
}
}
}
/*
* Cleanup.
*/
delete pState;
return VINF_SUCCESS; /* ignored */
}
/**
* Start teleporter to the specified target.
*
* @returns COM status code.
*
* @param aHostname The name of the target host.
* @param aPort The TCP port number.
* @param aPassword The password.
* @param aMaxDowntime Max allowed "downtime" in milliseconds.
* @param aProgress Where to return the progress object.
*/
Console::Teleport(IN_BSTR aHostname, ULONG aPort, IN_BSTR aPassword, ULONG aMaxDowntime, IProgress **aProgress)
{
/*
* Validate parameters, check+hold object status, write lock the object
* and validate the state.
*/
AutoCaller autoCaller(this);
switch (mMachineState)
{
case MachineState_Running:
case MachineState_Paused:
break;
default:
return setError(VBOX_E_INVALID_VM_STATE,
tr("Invalid machine state: %s (must be Running or Paused)"),
}
/*
* Create a progress object, spawn a worker thread and change the state.
* Note! The thread won't start working until we release the lock.
*/
LogFlowThisFunc(("Initiating TELEPORT request...\n"));
hrc = ptrProgress->init(static_cast<IConsole *>(this), Bstr(tr("Teleporter")), TRUE /*aCancelable*/);
if (RT_SUCCESS(vrc))
{
if (mMachineState == MachineState_Running)
else
{
}
else
ptrProgress->Cancel();
}
else
{
delete pState;
}
return hrc;
}
/**
* Creates a TCP server that listens for the source machine and passes control
* over to Console::teleporterTrgServeConnection().
*
* @returns VBox status code.
* @param pVM The VM handle
* @param pMachine The IMachine for the virtual machine.
* @param pErrorMsg Pointer to the error string for VMSetError.
* @param fStartPaused Whether to start it in the Paused (true) or
* Running (false) state,
* @param pProgress Pointer to the progress object.
* @param pfPowerOffOnFailure Whether the caller should power off
* the VM on failure.
*
* @remarks The caller expects error information to be set on failure.
* @todo Check that all the possible failure paths sets error info...
*/
{
LogThisFunc(("pVM=%p pMachine=%p fStartPaused=%RTbool pProgress=%p\n", pVM, pMachine, fStartPaused, pProgress));
*pfPowerOffOnFailure = true;
/*
* Get the config.
*/
return hrc;
return hrc;
return hrc;
/*
* Create the TCP server.
*/
int vrc;
if (uPort)
else
{
{
if (vrc != VERR_NET_ADDRESS_IN_USE)
break;
}
if (RT_SUCCESS(vrc))
{
{
return hrc;
}
}
}
if (RT_FAILURE(vrc))
/*
* Create a one-shot timer for timing out after 5 mins.
*/
if (RT_SUCCESS(vrc))
{
if (RT_SUCCESS(vrc))
{
/*
* Do the job, when it returns we're done.
*/
{
LogRel(("Teleporter: Waiting for incoming VM...\n"));
{
if (vrc == VERR_TCP_SERVER_STOP)
{
/* Power off the VM on failure unless the state callback
already did that. */
*pfPowerOffOnFailure = false;
if (RT_SUCCESS(vrc))
else
{
if ( enmVMState != VMSTATE_OFF
&& enmVMState != VMSTATE_POWERING_OFF)
*pfPowerOffOnFailure = true;
/* Set error. */
else
}
}
else if (vrc == VERR_TCP_SERVER_SHUTDOWN)
{
else
}
else
{
}
}
else
}
else
{
LogThisFunc(("Canceled - check point #1\n"));
}
}
else
}
else
/*
* If we change TeleporterPort above, set it back to it's original
* value before returning.
*/
{
}
return hrc;
}
/**
* Unlock the media.
*
* This is used in error paths.
*
* @param pState The teleporter state.
*/
{
if (pState->mfLockedMedia)
{
pState->mfLockedMedia = false;
}
}
{
if (RT_FAILURE(rc))
{
if (fAutomaticUnlock)
}
return rc;
}
{
/*
* Unlock media sending the NACK. That way the other doesn't have to spin
* waiting to regain the locks.
*/
char szMsg[64];
if (RT_FAILURE(rc))
return rc;
}
/**
* @copydoc FNRTTCPSERVE
*
* @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP.
*/
/*static*/ DECLCALLBACK(int)
{
/*
* Say hello.
*/
if (RT_FAILURE(vrc))
{
return VINF_SUCCESS;
}
/*
* Password (includes '\n', see teleporterTrg).
*/
unsigned off = 0;
while (pszPassword[off])
{
char ch;
if ( RT_FAILURE(vrc)
{
if (RT_FAILURE(vrc))
else
return VINF_SUCCESS;
}
off++;
}
if (RT_FAILURE(vrc))
return VINF_SUCCESS;
/*
* Update the progress bar, with peer name if available.
*/
if (RT_SUCCESS(vrc))
{
hrc = pState->mptrProgress->SetNextOperation(Bstr(Utf8StrFmt(tr("Teleporting VM from %RTnaddr"), &Addr)), 8);
}
else
{
LogRel(("Teleporter: Incoming VM!\n"));
}
/*
* Stop the server and cancel the timeout timer.
*
* Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
* to it we must not return that value!
*/
/*
* Command processing loop.
*/
bool fDone = false;
for (;;)
{
char szCmd[128];
if (RT_FAILURE(vrc))
break;
{
if (RT_FAILURE(vrc))
break;
pState->moffStream = 0;
if (RT_FAILURE(vrc))
{
break;
}
/* The EOS might not have been read, make sure it is. */
pState->mfStopReading = false;
{
break;
}
}
{
/* Don't ACK this. */
LogRel(("Teleporter: Received cancel command.\n"));
}
{
{
pState->mfLockedMedia = true;
}
else
{
}
}
{
/*
* Point of no return.
*
* Note! Since we cannot tell whether a VMR3Resume failure is
* destructive for the source or not, we have little choice
* but to ACK it first and take any failures locally.
*
* Ideally, we should try resume it first and then ACK (or
* NACK) the request since this would reduce latency and
* make it possible to recover from some VMR3Resume failures.
*/
&& pState->mfLockedMedia)
{
if (RT_SUCCESS(vrc))
{
else
fDone = true;
break;
}
}
else
{
}
}
else
{
}
if (RT_FAILURE(vrc))
break;
}
if (RT_FAILURE(vrc))
return VERR_TCP_SERVER_STOP;
}