VirtualBox

source: vbox/trunk/src/VBox/Main/ConsoleImplTeleporter.cpp@ 24905

Last change on this file since 24905 was 24899, checked in by vboxsync, 15 years ago

Main: Fixed VM power of during teleportation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.3 KB
Line 
1/* $Id: ConsoleImplTeleporter.cpp 24899 2009-11-24 13:31:21Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation, The Teleporter Part.
4 */
5
6/*
7 * Copyright (C) 2009 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#include "ConsoleImpl.h"
27#include "Global.h"
28#include "Logging.h"
29#include "ProgressImpl.h"
30
31#include <iprt/err.h>
32#include <iprt/rand.h>
33#include <iprt/tcp.h>
34#include <iprt/timer.h>
35
36#include <VBox/vmapi.h>
37#include <VBox/ssm.h>
38#include <VBox/err.h>
39#include <VBox/version.h>
40#include <VBox/com/string.h>
41
42
43/*******************************************************************************
44* Structures and Typedefs *
45*******************************************************************************/
46/**
47 * Base class for the teleporter state.
48 *
49 * These classes are used as advanced structs, not as proper classes.
50 */
51class TeleporterState
52{
53public:
54 ComPtr<Console> mptrConsole;
55 PVM mpVM;
56 ComObjPtr<Progress> mptrProgress;
57 Utf8Str mstrPassword;
58 bool const mfIsSource;
59
60 /** @name stream stuff
61 * @{ */
62 RTSOCKET mhSocket;
63 uint64_t moffStream;
64 uint32_t mcbReadBlock;
65 bool volatile mfStopReading;
66 bool volatile mfEndOfStream;
67 bool volatile mfIOError;
68 /** @} */
69
70 TeleporterState(Console *pConsole, PVM pVM, Progress *pProgress, bool fIsSource)
71 : mptrConsole(pConsole)
72 , mpVM(pVM)
73 , mptrProgress(pProgress)
74 , mfIsSource(fIsSource)
75 , mhSocket(NIL_RTSOCKET)
76 , moffStream(UINT64_MAX / 2)
77 , mcbReadBlock(0)
78 , mfStopReading(false)
79 , mfEndOfStream(false)
80 , mfIOError(false)
81 {
82 }
83};
84
85
86/**
87 * Teleporter state used by the source side.
88 */
89class TeleporterStateSrc : public TeleporterState
90{
91public:
92 Utf8Str mstrHostname;
93 uint32_t muPort;
94 uint32_t mcMsMaxDowntime;
95 MachineState_T menmOldMachineState;
96 bool mfSuspendedByUs;
97 bool mfUnlockedMedia;
98
99 TeleporterStateSrc(Console *pConsole, PVM pVM, Progress *pProgress, MachineState_T enmOldMachineState)
100 : TeleporterState(pConsole, pVM, pProgress, true /*fIsSource*/)
101 , muPort(UINT32_MAX)
102 , mcMsMaxDowntime(250)
103 , menmOldMachineState(enmOldMachineState)
104 , mfSuspendedByUs(false)
105 , mfUnlockedMedia(false)
106 {
107 }
108};
109
110
111/**
112 * Teleporter state used by the destiation side.
113 */
114class TeleporterStateTrg : public TeleporterState
115{
116public:
117 IMachine *mpMachine;
118 IInternalMachineControl *mpControl;
119 PRTTCPSERVER mhServer;
120 PRTTIMERLR mphTimerLR;
121 bool mfLockedMedia;
122 int mRc;
123
124 TeleporterStateTrg(Console *pConsole, PVM pVM, Progress *pProgress,
125 IMachine *pMachine, IInternalMachineControl *pControl,
126 PRTTIMERLR phTimerLR, bool fStartPaused)
127 : TeleporterState(pConsole, pVM, pProgress, false /*fIsSource*/)
128 , mpMachine(pMachine)
129 , mpControl(pControl)
130 , mhServer(NULL)
131 , mphTimerLR(phTimerLR)
132 , mfLockedMedia(false)
133 , mRc(VINF_SUCCESS)
134 {
135 }
136};
137
138
139/**
140 * TCP stream header.
141 *
142 * This is an extra layer for fixing the problem with figuring out when the SSM
143 * stream ends.
144 */
145typedef struct TELEPORTERTCPHDR
146{
147 /** Magic value. */
148 uint32_t u32Magic;
149 /** The size of the data block following this header.
150 * 0 indicates the end of the stream. */
151 uint32_t cb;
152} TELEPORTERTCPHDR;
153/** Magic value for TELEPORTERTCPHDR::u32Magic. (Egberto Gismonti Amin) */
154#define TELEPORTERTCPHDR_MAGIC UINT32_C(0x19471205)
155/** The max block size. */
156#define TELEPORTERTCPHDR_MAX_SIZE UINT32_C(0x00fffff8)
157
158
159/*******************************************************************************
160* Global Variables *
161*******************************************************************************/
162static const char g_szWelcome[] = "VirtualBox-Teleporter-1.0\n";
163
164
165/**
166 * Reads a string from the socket.
167 *
168 * @returns VBox status code.
169 *
170 * @param pState The teleporter state structure.
171 * @param pszBuf The output buffer.
172 * @param cchBuf The size of the output buffer.
173 *
174 */
175static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf)
176{
177 char *pszStart = pszBuf;
178 RTSOCKET Sock = pState->mhSocket;
179
180 AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR);
181 *pszBuf = '\0';
182
183 /* dead simple approach. */
184 for (;;)
185 {
186 char ch;
187 int rc = RTTcpRead(Sock, &ch, sizeof(ch), NULL);
188 if (RT_FAILURE(rc))
189 {
190 LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", rc, pszStart));
191 return rc;
192 }
193 if ( ch == '\n'
194 || ch == '\0')
195 return VINF_SUCCESS;
196 if (cchBuf <= 1)
197 {
198 LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart));
199 return VERR_BUFFER_OVERFLOW;
200 }
201 *pszBuf++ = ch;
202 *pszBuf = '\0';
203 cchBuf--;
204 }
205}
206
207
208/**
209 * Reads an ACK or NACK.
210 *
211 * @returns S_OK on ACK, E_FAIL+setError() on failure or NACK.
212 * @param pState The teleporter source state.
213 * @param pszWhich Which ACK is this this?
214 * @param pszNAckMsg Optional NACK message.
215 *
216 * @remarks the setError laziness forces this to be a Console member.
217 */
218HRESULT
219Console::teleporterSrcReadACK(TeleporterStateSrc *pState, const char *pszWhich,
220 const char *pszNAckMsg /*= NULL*/)
221{
222 char szMsg[128];
223 int vrc = teleporterTcpReadLine(pState, szMsg, sizeof(szMsg));
224 if (RT_FAILURE(vrc))
225 return setError(E_FAIL, tr("Failed reading ACK(%s): %Rrc"), pszWhich, vrc);
226 if (strcmp(szMsg, "ACK"))
227 {
228 if (!strncmp(szMsg, "NACK=", sizeof("NACK=") - 1))
229 {
230 int32_t vrc2;
231 vrc = RTStrToInt32Full(&szMsg[sizeof("NACK=") - 1], 10, &vrc2);
232 if (vrc == VINF_SUCCESS)
233 {
234 if (pszNAckMsg)
235 {
236 LogRel(("Teleporter: %s: NACK=%Rrc (%d)\n", pszWhich, vrc2, vrc2));
237 return setError(E_FAIL, pszNAckMsg);
238 }
239 return setError(E_FAIL, "NACK(%s) - %Rrc (%d)", pszWhich, vrc2, vrc2);
240 }
241 }
242 return setError(E_FAIL, tr("%s: Expected ACK or NACK, got '%s'"), pszWhich, szMsg);
243 }
244 return S_OK;
245}
246
247
248/**
249 * Submitts a command to the destination and waits for the ACK.
250 *
251 * @returns S_OK on ACKed command, E_FAIL+setError() on failure.
252 *
253 * @param pState The teleporter source state.
254 * @param pszCommand The command.
255 * @param fWaitForAck Whether to wait for the ACK.
256 *
257 * @remarks the setError laziness forces this to be a Console member.
258 */
259HRESULT
260Console::teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck /*= true*/)
261{
262 size_t cchCommand = strlen(pszCommand);
263 int vrc = RTTcpWrite(pState->mhSocket, pszCommand, cchCommand);
264 if (RT_SUCCESS(vrc))
265 vrc = RTTcpWrite(pState->mhSocket, "\n", sizeof("\n") - 1);
266 if (RT_SUCCESS(vrc))
267 vrc = RTTcpFlush(pState->mhSocket);
268 if (RT_FAILURE(vrc))
269 return setError(E_FAIL, tr("Failed writing command '%s': %Rrc"), pszCommand, vrc);
270 if (!fWaitForAck)
271 return S_OK;
272 return teleporterSrcReadACK(pState, pszCommand);
273}
274
275
276/**
277 * @copydoc SSMSTRMOPS::pfnWrite
278 */
279static DECLCALLBACK(int) teleporterTcpOpWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
280{
281 TeleporterState *pState = (TeleporterState *)pvUser;
282
283 AssertReturn(cbToWrite > 0, VINF_SUCCESS);
284 AssertReturn(cbToWrite < UINT32_MAX, VERR_OUT_OF_RANGE);
285 AssertReturn(pState->mfIsSource, VERR_INVALID_HANDLE);
286
287 for (;;)
288 {
289 /* Write block header. */
290 TELEPORTERTCPHDR Hdr;
291 Hdr.u32Magic = TELEPORTERTCPHDR_MAGIC;
292 Hdr.cb = RT_MIN((uint32_t)cbToWrite, TELEPORTERTCPHDR_MAX_SIZE);
293 int rc = RTTcpWrite(pState->mhSocket, &Hdr, sizeof(Hdr));
294 if (RT_FAILURE(rc))
295 {
296 LogRel(("Teleporter/TCP: Header write error: %Rrc\n", rc));
297 return rc;
298 }
299
300 /* Write the data. */
301 rc = RTTcpWrite(pState->mhSocket, pvBuf, Hdr.cb);
302 if (RT_FAILURE(rc))
303 {
304 LogRel(("Teleporter/TCP: Data write error: %Rrc (cb=%#x)\n", rc, Hdr.cb));
305 return rc;
306 }
307 pState->moffStream += Hdr.cb;
308 if (Hdr.cb == cbToWrite)
309 return VINF_SUCCESS;
310
311 /* advance */
312 cbToWrite -= Hdr.cb;
313 pvBuf = (uint8_t const *)pvBuf + Hdr.cb;
314 }
315}
316
317
318/**
319 * Selects and poll for close condition.
320 *
321 * We can use a relatively high poll timeout here since it's only used to get
322 * us out of error paths. In the normal cause of events, we'll get a
323 * end-of-stream header.
324 *
325 * @returns VBox status code.
326 *
327 * @param pState The teleporter state data.
328 */
329static int teleporterTcpReadSelect(TeleporterState *pState)
330{
331 int rc;
332 do
333 {
334 rc = RTTcpSelectOne(pState->mhSocket, 1000);
335 if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
336 {
337 pState->mfIOError = true;
338 LogRel(("Teleporter/TCP: Header select error: %Rrc\n", rc));
339 break;
340 }
341 if (pState->mfStopReading)
342 {
343 rc = VERR_EOF;
344 break;
345 }
346 } while (rc == VERR_TIMEOUT);
347 return rc;
348}
349
350
351/**
352 * @copydoc SSMSTRMOPS::pfnRead
353 */
354static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
355{
356 TeleporterState *pState = (TeleporterState *)pvUser;
357 AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE);
358
359 for (;;)
360 {
361 int rc;
362
363 /*
364 * Check for various conditions and may have been signalled.
365 */
366 if (pState->mfEndOfStream)
367 return VERR_EOF;
368 if (pState->mfStopReading)
369 return VERR_EOF;
370 if (pState->mfIOError)
371 return VERR_IO_GEN_FAILURE;
372
373 /*
374 * If there is no more data in the current block, read the next
375 * block header.
376 */
377 if (!pState->mcbReadBlock)
378 {
379 rc = teleporterTcpReadSelect(pState);
380 if (RT_FAILURE(rc))
381 return rc;
382 TELEPORTERTCPHDR Hdr;
383 rc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL);
384 if (RT_FAILURE(rc))
385 {
386 pState->mfIOError = true;
387 LogRel(("Teleporter/TCP: Header read error: %Rrc\n", rc));
388 return rc;
389 }
390 if ( Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC
391 || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE)
392 {
393 pState->mfIOError = true;
394 LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb));
395 return VERR_IO_GEN_FAILURE;
396 }
397
398 pState->mcbReadBlock = Hdr.cb;
399 if (!Hdr.cb)
400 {
401 pState->mfEndOfStream = true;
402 return VERR_EOF;
403 }
404
405 if (pState->mfStopReading)
406 return VERR_EOF;
407 }
408
409 /*
410 * Read more data.
411 */
412 rc = teleporterTcpReadSelect(pState);
413 if (RT_FAILURE(rc))
414 return rc;
415 uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead);
416 rc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead);
417 if (RT_FAILURE(rc))
418 {
419 pState->mfIOError = true;
420 LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", rc, cb));
421 return rc;
422 }
423 if (pcbRead)
424 {
425 cb = (uint32_t)*pcbRead;
426 pState->moffStream += cb;
427 pState->mcbReadBlock -= cb;
428 return VINF_SUCCESS;
429 }
430 pState->moffStream += cb;
431 pState->mcbReadBlock -= cb;
432 if (cbToRead == cb)
433 return VINF_SUCCESS;
434
435 /* Advance to the next block. */
436 cbToRead -= cb;
437 pvBuf = (uint8_t *)pvBuf + cb;
438 }
439}
440
441
442/**
443 * @copydoc SSMSTRMOPS::pfnSeek
444 */
445static DECLCALLBACK(int) teleporterTcpOpSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
446{
447 return VERR_NOT_SUPPORTED;
448}
449
450
451/**
452 * @copydoc SSMSTRMOPS::pfnTell
453 */
454static DECLCALLBACK(uint64_t) teleporterTcpOpTell(void *pvUser)
455{
456 TeleporterState *pState = (TeleporterState *)pvUser;
457 return pState->moffStream;
458}
459
460
461/**
462 * @copydoc SSMSTRMOPS::pfnSize
463 */
464static DECLCALLBACK(int) teleporterTcpOpSize(void *pvUser, uint64_t *pcb)
465{
466 return VERR_NOT_SUPPORTED;
467}
468
469
470/**
471 * @copydoc SSMSTRMOPS::pfnIsOk
472 */
473static DECLCALLBACK(int) teleporterTcpOpIsOk(void *pvUser)
474{
475 TeleporterState *pState = (TeleporterState *)pvUser;
476
477 if (pState->mfIsSource)
478 {
479 /* Poll for incoming NACKs and errors from the other side */
480 int rc = RTTcpSelectOne(pState->mhSocket, 0);
481 if (rc != VERR_TIMEOUT)
482 {
483 if (RT_SUCCESS(rc))
484 {
485 LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n"));
486 rc = VERR_SSM_CANCELLED;
487 }
488 else
489 LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", rc));
490 return rc;
491 }
492 }
493
494 return VINF_SUCCESS;
495}
496
497
498/**
499 * @copydoc SSMSTRMOPS::pfnClose
500 */
501static DECLCALLBACK(int) teleporterTcpOpClose(void *pvUser)
502{
503 TeleporterState *pState = (TeleporterState *)pvUser;
504
505 if (pState->mfIsSource)
506 {
507 TELEPORTERTCPHDR EofHdr = { TELEPORTERTCPHDR_MAGIC, 0 };
508 int rc = RTTcpWrite(pState->mhSocket, &EofHdr, sizeof(EofHdr));
509 if (RT_SUCCESS(rc))
510 rc = RTTcpFlush(pState->mhSocket);
511 if (RT_FAILURE(rc))
512 {
513 LogRel(("Teleporter/TCP: EOF Header write error: %Rrc\n", rc));
514 return rc;
515 }
516 }
517 else
518 {
519 ASMAtomicWriteBool(&pState->mfStopReading, true);
520 RTTcpFlush(pState->mhSocket);
521 }
522
523 return VINF_SUCCESS;
524}
525
526
527/**
528 * Method table for a TCP based stream.
529 */
530static SSMSTRMOPS const g_teleporterTcpOps =
531{
532 SSMSTRMOPS_VERSION,
533 teleporterTcpOpWrite,
534 teleporterTcpOpRead,
535 teleporterTcpOpSeek,
536 teleporterTcpOpTell,
537 teleporterTcpOpSize,
538 teleporterTcpOpIsOk,
539 teleporterTcpOpClose,
540 SSMSTRMOPS_VERSION
541};
542
543
544/**
545 * Progress cancelation callback.
546 */
547static void teleporterProgressCancelCallback(void *pvUser)
548{
549 TeleporterState *pState = (TeleporterState *)pvUser;
550 SSMR3Cancel(pState->mpVM);
551 if (!pState->mfIsSource)
552 {
553 TeleporterStateTrg *pStateTrg = (TeleporterStateTrg *)pState;
554 RTTcpServerShutdown(pStateTrg->mhServer);
555 }
556}
557
558/**
559 * @copydoc PFNVMPROGRESS
560 */
561static DECLCALLBACK(int) teleporterProgressCallback(PVM pVM, unsigned uPercent, void *pvUser)
562{
563 TeleporterState *pState = (TeleporterState *)pvUser;
564 if (pState->mptrProgress)
565 {
566 HRESULT hrc = pState->mptrProgress->SetCurrentOperationProgress(uPercent);
567 if (FAILED(hrc))
568 {
569 /* check if the failure was caused by cancellation. */
570 BOOL fCancelled;
571 hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCancelled);
572 if (SUCCEEDED(hrc) && fCancelled)
573 {
574 SSMR3Cancel(pState->mpVM);
575 return VERR_SSM_CANCELLED;
576 }
577 }
578 }
579
580 return VINF_SUCCESS;
581}
582
583
584/**
585 * @copydoc FNRTTIMERLR
586 */
587static DECLCALLBACK(void) teleporterDstTimeout(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
588{
589 /* This is harmless for any open connections. */
590 RTTcpServerShutdown((PRTTCPSERVER)pvUser);
591}
592
593
594/**
595 * Do the teleporter.
596 *
597 * @returns VBox status code.
598 * @param pState The teleporter state.
599 */
600HRESULT
601Console::teleporterSrc(TeleporterStateSrc *pState)
602{
603 AutoCaller autoCaller(this);
604 CheckComRCReturnRC(autoCaller.rc());
605
606 /*
607 * Wait for Console::Teleport to change the state.
608 */
609 { AutoWriteLock autoLock(this); }
610
611 BOOL fCancelled = TRUE;
612 HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCancelled);
613 if (FAILED(hrc))
614 return hrc;
615 if (fCancelled)
616 return setError(E_FAIL, tr("cancelled"));
617
618 /*
619 * Try connect to the destination machine.
620 * (Note. The caller cleans up mhSocket, so we can return without worries.)
621 */
622 int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket);
623 if (RT_FAILURE(vrc))
624 return setError(E_FAIL, tr("Failed to connect to port %u on '%s': %Rrc"),
625 pState->muPort, pState->mstrHostname.c_str(), vrc);
626
627 /* Read and check the welcome message. */
628 char szLine[RT_MAX(128, sizeof(g_szWelcome))];
629 RT_ZERO(szLine);
630 vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL);
631 if (RT_FAILURE(vrc))
632 return setError(E_FAIL, tr("Failed to read welcome message: %Rrc"), vrc);
633 if (strcmp(szLine, g_szWelcome))
634 return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine);
635
636 /* password */
637 pState->mstrPassword.append('\n');
638 vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length());
639 if (RT_FAILURE(vrc))
640 return setError(E_FAIL, tr("Failed to send password: %Rrc"), vrc);
641
642 /* ACK */
643 hrc = teleporterSrcReadACK(pState, "password", tr("Invalid password"));
644 if (FAILED(hrc))
645 return hrc;
646
647 /*
648 * Start loading the state.
649 *
650 * Note! The saved state includes vital configuration data which will be
651 * verified against the VM config on the other end. This is all done
652 * in the first pass, so we should fail pretty promptly on misconfig.
653 */
654 hrc = teleporterSrcSubmitCommand(pState, "load");
655 if (FAILED(hrc))
656 return hrc;
657
658 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
659 vrc = VMR3Teleport(pState->mpVM, pState->mcMsMaxDowntime,
660 &g_teleporterTcpOps, pvUser,
661 teleporterProgressCallback, pvUser,
662 &pState->mfSuspendedByUs);
663 if (RT_FAILURE(vrc))
664 return setError(E_FAIL, tr("VMR3Teleport -> %Rrc"), vrc);
665
666 hrc = teleporterSrcReadACK(pState, "load-complete");
667 if (FAILED(hrc))
668 return hrc;
669
670 /*
671 * We're at the point of no return.
672 */
673 if (!pState->mptrProgress->notifyPointOfNoReturn())
674 {
675 teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/);
676 return E_FAIL;
677 }
678
679 /*
680 * Hand over any media which we might be sharing.
681 *
682 * Note! This is only important on localhost teleportations.
683 */
684 /** @todo Maybe we should only do this if it's a local teleportation... */
685 hrc = mControl->UnlockMedia();
686 if (FAILED(hrc))
687 return hrc;
688 pState->mfUnlockedMedia = true;
689
690 hrc = teleporterSrcSubmitCommand(pState, "lock-media");
691 if (FAILED(hrc))
692 return hrc;
693
694 /*
695 * The FINAL step is giving the target instructions how to proceed with the VM.
696 */
697 if ( vrc == VINF_SSM_LIVE_SUSPENDED
698 || pState->menmOldMachineState == MachineState_Paused)
699 hrc = teleporterSrcSubmitCommand(pState, "hand-over-paused");
700 else
701 hrc = teleporterSrcSubmitCommand(pState, "hand-over-resume");
702 if (FAILED(hrc))
703 return hrc;
704
705 /*
706 * teleporterSrcThreadWrapper will do the automatic power off because it
707 * has to release the AutoVMCaller.
708 */
709 return S_OK;
710}
711
712
713/**
714 * Static thread method wrapper.
715 *
716 * @returns VINF_SUCCESS (ignored).
717 * @param hThread The thread.
718 * @param pvUser Pointer to a TeleporterStateSrc instance.
719 */
720/*static*/ DECLCALLBACK(int)
721Console::teleporterSrcThreadWrapper(RTTHREAD hThread, void *pvUser)
722{
723 TeleporterStateSrc *pState = (TeleporterStateSrc *)pvUser;
724
725 /*
726 * Console::teleporterSrc does the work, we just grab onto the VM handle
727 * and do the cleanups afterwards.
728 */
729 AutoVMCaller autoVMCaller(pState->mptrConsole);
730 HRESULT hrc = autoVMCaller.rc();
731
732 if (SUCCEEDED(hrc))
733 hrc = pState->mptrConsole->teleporterSrc(pState);
734
735 /* Close the connection ASAP on so that the other side can complete. */
736 if (pState->mhSocket != NIL_RTSOCKET)
737 {
738 RTTcpClientClose(pState->mhSocket);
739 pState->mhSocket = NIL_RTSOCKET;
740 }
741
742 /* Aaarg! setMachineState trashes error info on Windows, so we have to
743 complete things here on failure instead of right before cleanup. */
744 if (FAILED(hrc))
745 pState->mptrProgress->notifyComplete(hrc);
746
747 /* We can no longer be cancelled (success), or it doesn't matter any longer (failure). */
748 pState->mptrProgress->setCancelCallback(NULL, NULL);
749
750 /*
751 * Write lock the console before resetting mptrCancelableProgress and
752 * fixing the state.
753 */
754 AutoWriteLock autoLock(pState->mptrConsole);
755 pState->mptrConsole->mptrCancelableProgress.setNull();
756
757 VMSTATE const enmVMState = VMR3GetState(pState->mpVM);
758 MachineState_T const enmMachineState = pState->mptrConsole->mMachineState;
759 if (SUCCEEDED(hrc))
760 {
761 /*
762 * Automatically shut down the VM on success.
763 *
764 * Note! We have to release the VM caller object or we'll deadlock in
765 * powerDown.
766 */
767 AssertLogRelMsg(enmVMState == VMSTATE_SUSPENDED, ("%s\n", VMR3GetStateName(enmVMState)));
768 AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, ("%s\n", Global::stringifyMachineState(enmMachineState)));
769
770 autoVMCaller.release();
771
772 pState->mptrConsole->mVMIsAlreadyPoweringOff = true; /* (Make sure we stick in the TeleportingPausedVM state.) */
773 hrc = pState->mptrConsole->powerDown();
774 pState->mptrConsole->mVMIsAlreadyPoweringOff = false;
775
776 pState->mptrProgress->notifyComplete(hrc);
777 }
778 else
779 {
780 /*
781 * Work the state machinery on failure.
782 *
783 * If the state is no longer 'Teleporting*', some other operation has
784 * canceled us and there is nothing we need to do here. In all other
785 * cases, we've failed one way or another.
786 */
787 if ( enmMachineState == MachineState_Teleporting
788 || enmMachineState == MachineState_TeleportingPausedVM
789 )
790 {
791 if (pState->mfUnlockedMedia)
792 {
793 ErrorInfoKeeper Oak;
794 HRESULT hrc2 = pState->mptrConsole->mControl->LockMedia();
795 if (FAILED(hrc2))
796 {
797 uint64_t StartMS = RTTimeMilliTS();
798 do
799 {
800 RTThreadSleep(2);
801 hrc2 = pState->mptrConsole->mControl->LockMedia();
802 } while ( FAILED(hrc2)
803 && RTTimeMilliTS() - StartMS < 2000);
804 }
805 if (SUCCEEDED(hrc2))
806 pState->mfUnlockedMedia = true;
807 else
808 LogRel(("FATAL ERROR: Failed to re-take the media locks. hrc2=%Rhrc\n", hrc2));
809 }
810
811 switch (enmVMState)
812 {
813 case VMSTATE_RUNNING:
814 case VMSTATE_RUNNING_LS:
815 case VMSTATE_DEBUGGING:
816 case VMSTATE_DEBUGGING_LS:
817 case VMSTATE_POWERING_OFF:
818 case VMSTATE_POWERING_OFF_LS:
819 case VMSTATE_RESETTING:
820 case VMSTATE_RESETTING_LS:
821 Assert(!pState->mfSuspendedByUs);
822 Assert(!pState->mfUnlockedMedia);
823 pState->mptrConsole->setMachineState(MachineState_Running);
824 break;
825
826 case VMSTATE_GURU_MEDITATION:
827 case VMSTATE_GURU_MEDITATION_LS:
828 pState->mptrConsole->setMachineState(MachineState_Stuck);
829 break;
830
831 case VMSTATE_FATAL_ERROR:
832 case VMSTATE_FATAL_ERROR_LS:
833 pState->mptrConsole->setMachineState(MachineState_Paused);
834 break;
835
836 default:
837 AssertMsgFailed(("%s\n", VMR3GetStateName(enmVMState)));
838 case VMSTATE_SUSPENDED:
839 case VMSTATE_SUSPENDED_LS:
840 case VMSTATE_SUSPENDING:
841 case VMSTATE_SUSPENDING_LS:
842 case VMSTATE_SUSPENDING_EXT_LS:
843 if (!pState->mfUnlockedMedia)
844 {
845 pState->mptrConsole->setMachineState(MachineState_Paused);
846 if (pState->mfSuspendedByUs)
847 {
848 autoLock.leave();
849 int rc = VMR3Resume(pState->mpVM);
850 AssertLogRelMsgRC(rc, ("VMR3Resume -> %Rrc\n", rc));
851 autoLock.enter();
852 }
853 }
854 else
855 {
856 /* Faking a guru meditation is the best I can think of doing here... */
857 pState->mptrConsole->setMachineState(MachineState_Stuck);
858 }
859 break;
860 }
861 }
862 }
863 autoLock.leave();
864
865 /*
866 * Cleanup.
867 */
868 Assert(pState->mhSocket == NIL_RTSOCKET);
869 delete pState;
870
871 return VINF_SUCCESS; /* ignored */
872}
873
874
875/**
876 * Start teleporter to the specified target.
877 *
878 * @returns COM status code.
879 *
880 * @param aHostname The name of the target host.
881 * @param aPort The TCP port number.
882 * @param aPassword The password.
883 * @param aMaxDowntime Max allowed "downtime" in milliseconds.
884 * @param aProgress Where to return the progress object.
885 */
886STDMETHODIMP
887Console::Teleport(IN_BSTR aHostname, ULONG aPort, IN_BSTR aPassword, ULONG aMaxDowntime, IProgress **aProgress)
888{
889 /*
890 * Validate parameters, check+hold object status, write lock the object
891 * and validate the state.
892 */
893 CheckComArgOutPointerValid(aProgress);
894 CheckComArgStrNotEmptyOrNull(aHostname);
895 CheckComArgNotNull(aHostname);
896 CheckComArgExprMsg(aPort, aPort > 0 && aPort <= 65535, ("is %u", aPort));
897 CheckComArgExprMsg(aMaxDowntime, aMaxDowntime > 0, ("is %u", aMaxDowntime));
898
899 AutoCaller autoCaller(this);
900 CheckComRCReturnRC(autoCaller.rc());
901
902 AutoWriteLock autoLock(this);
903 LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
904
905 switch (mMachineState)
906 {
907 case MachineState_Running:
908 case MachineState_Paused:
909 break;
910
911 default:
912 return setError(VBOX_E_INVALID_VM_STATE,
913 tr("Invalid machine state: %s (must be Running or Paused)"),
914 Global::stringifyMachineState(mMachineState));
915 }
916
917
918 /*
919 * Create a progress object, spawn a worker thread and change the state.
920 * Note! The thread won't start working until we release the lock.
921 */
922 LogFlowThisFunc(("Initiating TELEPORT request...\n"));
923
924 ComObjPtr<Progress> ptrProgress;
925 HRESULT hrc = ptrProgress.createObject();
926 CheckComRCReturnRC(hrc);
927 hrc = ptrProgress->init(static_cast<IConsole *>(this), Bstr(tr("Teleporter")), TRUE /*aCancelable*/);
928 CheckComRCReturnRC(hrc);
929
930 TeleporterStateSrc *pState = new TeleporterStateSrc(this, mpVM, ptrProgress, mMachineState);
931 pState->mstrPassword = aPassword;
932 pState->mstrHostname = aHostname;
933 pState->muPort = aPort;
934 pState->mcMsMaxDowntime = aMaxDowntime;
935
936 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
937 ptrProgress->setCancelCallback(teleporterProgressCancelCallback, pvUser);
938
939 int vrc = RTThreadCreate(NULL, Console::teleporterSrcThreadWrapper, (void *)pState, 0 /*cbStack*/,
940 RTTHREADTYPE_EMULATION, 0 /*fFlags*/, "Teleport");
941 if (RT_SUCCESS(vrc))
942 {
943 if (mMachineState == MachineState_Running)
944 hrc = setMachineState(MachineState_Teleporting);
945 else
946 hrc = setMachineState(MachineState_TeleportingPausedVM);
947 if (SUCCEEDED(hrc))
948 {
949 ptrProgress.queryInterfaceTo(aProgress);
950 mptrCancelableProgress = ptrProgress;
951 }
952 else
953 ptrProgress->Cancel();
954 }
955 else
956 {
957 ptrProgress->setCancelCallback(NULL, NULL);
958 delete pState;
959 hrc = setError(E_FAIL, tr("RTThreadCreate -> %Rrc"), vrc);
960 }
961
962 return hrc;
963}
964
965
966/**
967 * Creates a TCP server that listens for the source machine and passes control
968 * over to Console::teleporterTrgServeConnection().
969 *
970 * @returns VBox status code.
971 * @param pVM The VM handle
972 * @param pMachine The IMachine for the virtual machine.
973 * @param fStartPaused Whether to start it in the Paused (true) or
974 * Running (false) state,
975 * @param pProgress Pointer to the progress object.
976 *
977 * @remarks The caller expects error information to be set on failure.
978 * @todo Check that all the possible failure paths sets error info...
979 */
980int
981Console::teleporterTrg(PVM pVM, IMachine *pMachine, bool fStartPaused, Progress *pProgress)
982{
983 /*
984 * Get the config.
985 */
986 ULONG uPort;
987 HRESULT hrc = pMachine->COMGETTER(TeleporterPort)(&uPort);
988 if (FAILED(hrc))
989 return VERR_GENERAL_FAILURE;
990 ULONG const uPortOrg = uPort;
991
992 Bstr bstrAddress;
993 hrc = pMachine->COMGETTER(TeleporterAddress)(bstrAddress.asOutParam());
994 if (FAILED(hrc))
995 return VERR_GENERAL_FAILURE;
996 Utf8Str strAddress(bstrAddress);
997 const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str();
998
999 Bstr bstrPassword;
1000 hrc = pMachine->COMGETTER(TeleporterPassword)(bstrPassword.asOutParam());
1001 if (FAILED(hrc))
1002 return VERR_GENERAL_FAILURE;
1003 Utf8Str strPassword(bstrPassword);
1004 strPassword.append('\n'); /* To simplify password checking. */
1005
1006 /*
1007 * Create the TCP server.
1008 */
1009 int vrc;
1010 PRTTCPSERVER hServer;
1011 if (uPort)
1012 vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
1013 else
1014 {
1015 for (int cTries = 10240; cTries > 0; cTries--)
1016 {
1017 uPort = RTRandU32Ex(cTries >= 8192 ? 49152 : 1024, 65534);
1018 vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
1019 if (vrc != VERR_NET_ADDRESS_IN_USE)
1020 break;
1021 }
1022 if (RT_SUCCESS(vrc))
1023 {
1024 hrc = pMachine->COMSETTER(TeleporterPort)(uPort);
1025 if (FAILED(hrc))
1026 {
1027 RTTcpServerDestroy(hServer);
1028 return VERR_GENERAL_FAILURE;
1029 }
1030 }
1031 }
1032 if (RT_FAILURE(vrc))
1033 return vrc;
1034
1035 /*
1036 * Create a one-shot timer for timing out after 5 mins.
1037 */
1038 RTTIMERLR hTimerLR;
1039 vrc = RTTimerLRCreateEx(&hTimerLR, 0 /*ns*/, RTTIMER_FLAGS_CPU_ANY, teleporterDstTimeout, hServer);
1040 if (RT_SUCCESS(vrc))
1041 {
1042 vrc = RTTimerLRStart(hTimerLR, 5*60*UINT64_C(1000000000) /*ns*/);
1043 if (RT_SUCCESS(vrc))
1044 {
1045 /*
1046 * Do the job, when it returns we're done.
1047 */
1048 TeleporterStateTrg State(this, pVM, pProgress, pMachine, mControl, &hTimerLR, fStartPaused);
1049 State.mstrPassword = strPassword;
1050 State.mhServer = hServer;
1051
1052 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(&State));
1053 if (pProgress->setCancelCallback(teleporterProgressCancelCallback, pvUser))
1054 {
1055 LogRel(("Teleporter: Waiting for incoming VM...\n"));
1056 vrc = RTTcpServerListen(hServer, Console::teleporterTrgServeConnection, &State);
1057 pProgress->setCancelCallback(NULL, NULL);
1058
1059 bool fPowerOff = false;
1060 if (vrc == VERR_TCP_SERVER_STOP)
1061 {
1062 vrc = State.mRc;
1063 /* Power off the VM on failure unless the state callback
1064 already did that. */
1065 if (RT_FAILURE(vrc))
1066 {
1067 VMSTATE enmVMState = VMR3GetState(pVM);
1068 if ( enmVMState != VMSTATE_OFF
1069 && enmVMState != VMSTATE_POWERING_OFF)
1070 fPowerOff = true;
1071 }
1072 }
1073 else if (vrc == VERR_TCP_SERVER_SHUTDOWN)
1074 {
1075 BOOL fCancelled = TRUE;
1076 hrc = pProgress->COMGETTER(Canceled)(&fCancelled);
1077 if (FAILED(hrc) || fCancelled)
1078 {
1079 setError(E_FAIL, tr("Teleporting canceled"));
1080 vrc = VERR_SSM_CANCELLED;
1081 }
1082 else
1083 {
1084 setError(E_FAIL, tr("Teleporter timed out waiting for incoming connection"));
1085 vrc = VERR_TIMEOUT;
1086 }
1087 LogRel(("Teleporter: RTTcpServerListen aborted - %Rrc\n", vrc));
1088 fPowerOff = true;
1089 }
1090 else
1091 {
1092 LogRel(("Teleporter: Unexpected RTTcpServerListen rc: %Rrc\n", vrc));
1093 vrc = VERR_IPE_UNEXPECTED_STATUS;
1094 fPowerOff = true;
1095 }
1096
1097 if (fPowerOff)
1098 {
1099 int vrc2 = VMR3PowerOff(pVM);
1100 AssertRC(vrc2);
1101 }
1102 }
1103 else
1104 vrc = VERR_SSM_CANCELLED;
1105 }
1106
1107 RTTimerLRDestroy(hTimerLR);
1108 }
1109 RTTcpServerDestroy(hServer);
1110
1111 /*
1112 * If we change TeleporterPort above, set it back to it's original
1113 * value before returning.
1114 */
1115 if (uPortOrg != uPort)
1116 pMachine->COMSETTER(TeleporterPort)(uPortOrg);
1117
1118 return vrc;
1119}
1120
1121
1122/**
1123 * Unlock the media.
1124 *
1125 * This is used in error paths.
1126 *
1127 * @param pState The teleporter state.
1128 */
1129static void teleporterTrgUnlockMedia(TeleporterStateTrg *pState)
1130{
1131 if (pState->mfLockedMedia)
1132 {
1133 pState->mpControl->UnlockMedia();
1134 pState->mfLockedMedia = false;
1135 }
1136}
1137
1138
1139static int teleporterTcpWriteACK(TeleporterStateTrg *pState, bool fAutomaticUnlock = true)
1140{
1141 int rc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1);
1142 if (RT_FAILURE(rc))
1143 {
1144 LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", rc));
1145 if (fAutomaticUnlock)
1146 teleporterTrgUnlockMedia(pState);
1147 }
1148 RTTcpFlush(pState->mhSocket);
1149 return rc;
1150}
1151
1152
1153static int teleporterTcpWriteNACK(TeleporterStateTrg *pState, int32_t rc2)
1154{
1155 /*
1156 * Unlock media sending the NACK. That way the other doesn't have to spin
1157 * waiting to regain the locks.
1158 */
1159 teleporterTrgUnlockMedia(pState);
1160
1161 char szMsg[64];
1162 size_t cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d\n", rc2);
1163 int rc = RTTcpWrite(pState->mhSocket, szMsg, cch);
1164 if (RT_FAILURE(rc))
1165 LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, rc));
1166 RTTcpFlush(pState->mhSocket);
1167 return rc;
1168}
1169
1170
1171/**
1172 * @copydoc FNRTTCPSERVE
1173 *
1174 * @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP.
1175 */
1176/*static*/ DECLCALLBACK(int)
1177Console::teleporterTrgServeConnection(RTSOCKET Sock, void *pvUser)
1178{
1179 TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser;
1180 pState->mhSocket = Sock;
1181
1182 /*
1183 * Say hello.
1184 */
1185 int vrc = RTTcpWrite(Sock, g_szWelcome, sizeof(g_szWelcome) - 1);
1186 if (RT_FAILURE(vrc))
1187 {
1188 LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc));
1189 return VINF_SUCCESS;
1190 }
1191
1192 /*
1193 * Password (includes '\n', see teleporterTrg).
1194 */
1195 const char *pszPassword = pState->mstrPassword.c_str();
1196 unsigned off = 0;
1197 while (pszPassword[off])
1198 {
1199 char ch;
1200 vrc = RTTcpRead(Sock, &ch, sizeof(ch), NULL);
1201 if ( RT_FAILURE(vrc)
1202 || pszPassword[off] != ch)
1203 {
1204 if (RT_FAILURE(vrc))
1205 LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc));
1206 else
1207 LogRel(("Teleporter: Invalid password (off=%u)\n", off));
1208 teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE);
1209 return VINF_SUCCESS;
1210 }
1211 off++;
1212 }
1213 vrc = teleporterTcpWriteACK(pState);
1214 if (RT_FAILURE(vrc))
1215 return VINF_SUCCESS;
1216 LogRel(("Teleporter: Incoming VM!\n"));
1217
1218 /*
1219 * Stop the server and cancel the timeout timer.
1220 *
1221 * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
1222 * to it we must not return that value!
1223 */
1224 RTTcpServerShutdown(pState->mhServer);
1225 RTTimerLRDestroy(*pState->mphTimerLR);
1226 *pState->mphTimerLR = NIL_RTTIMERLR;
1227
1228 /*
1229 * Command processing loop.
1230 */
1231 bool fDone = false;
1232 for (;;)
1233 {
1234 char szCmd[128];
1235 vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd));
1236 if (RT_FAILURE(vrc))
1237 break;
1238
1239 if (!strcmp(szCmd, "load"))
1240 {
1241 vrc = teleporterTcpWriteACK(pState);
1242 if (RT_FAILURE(vrc))
1243 break;
1244
1245 pState->moffStream = 0;
1246 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
1247 vrc = VMR3LoadFromStream(pState->mpVM, &g_teleporterTcpOps, pvUser,
1248 teleporterProgressCallback, pvUser);
1249 if (RT_FAILURE(vrc))
1250 {
1251 LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc));
1252 teleporterTcpWriteNACK(pState, vrc);
1253 break;
1254 }
1255
1256 /* The EOS might not have been read, make sure it is. */
1257 pState->mfStopReading = false;
1258 size_t cbRead;
1259 vrc = teleporterTcpOpRead(pvUser, pState->moffStream, szCmd, 1, &cbRead);
1260 if (vrc != VERR_EOF)
1261 {
1262 LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc));
1263 teleporterTcpWriteNACK(pState, vrc);
1264 break;
1265 }
1266
1267 vrc = teleporterTcpWriteACK(pState);
1268 }
1269 else if (!strcmp(szCmd, "cancel"))
1270 {
1271 /* Don't ACK this. */
1272 LogRel(("Teleporter: Received cancel command.\n"));
1273 vrc = VERR_SSM_CANCELLED;
1274 }
1275 else if (!strcmp(szCmd, "lock-media"))
1276 {
1277 HRESULT hrc = pState->mpControl->LockMedia();
1278 if (SUCCEEDED(hrc))
1279 {
1280 pState->mfLockedMedia = true;
1281 vrc = teleporterTcpWriteACK(pState);
1282 }
1283 else
1284 {
1285 vrc = VERR_FILE_LOCK_FAILED;
1286 teleporterTcpWriteNACK(pState, vrc);
1287 }
1288 }
1289 else if ( !strcmp(szCmd, "hand-over-resume")
1290 || !strcmp(szCmd, "hand-over-paused"))
1291 {
1292 /*
1293 * Point of no return.
1294 *
1295 * Note! Since we cannot tell whether a VMR3Resume failure is
1296 * destructive for the source or not, we have little choice
1297 * but to ACK it first and take any failures locally.
1298 *
1299 * Ideally, we should try resume it first and then ACK (or
1300 * NACK) the request since this would reduce latency and
1301 * make it possible to recover from some VMR3Resume failures.
1302 */
1303 if ( pState->mptrProgress->notifyPointOfNoReturn()
1304 && pState->mfLockedMedia)
1305 {
1306 vrc = teleporterTcpWriteACK(pState);
1307 if (RT_SUCCESS(vrc))
1308 {
1309 if (!strcmp(szCmd, "hand-over-resume"))
1310 vrc = VMR3Resume(pState->mpVM);
1311 else
1312 pState->mptrConsole->setMachineState(MachineState_Paused);
1313 fDone = true;
1314 break;
1315 }
1316 }
1317 else
1318 {
1319 vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED;
1320 teleporterTcpWriteNACK(pState, vrc);
1321 }
1322 }
1323 else
1324 {
1325 LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd));
1326 vrc = VERR_NOT_IMPLEMENTED;
1327 teleporterTcpWriteNACK(pState, vrc);
1328 }
1329
1330 if (RT_FAILURE(vrc))
1331 break;
1332 }
1333
1334 if (RT_SUCCESS(vrc) && !fDone)
1335 vrc = VERR_WRONG_ORDER;
1336 if (RT_FAILURE(vrc))
1337 teleporterTrgUnlockMedia(pState);
1338
1339 pState->mRc = vrc;
1340 pState->mhSocket = NIL_RTSOCKET;
1341 LogFlowFunc(("returns mRc=%Rrc\n", vrc));
1342 return VERR_TCP_SERVER_STOP;
1343}
1344
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette