VirtualBox

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

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

SSM: Flush buffers when they are 0.5 sec old and check that the stream is ok (hasn't been cancelled). Check for out of disk space before each write.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.1 KB
Line 
1/* $Id: ConsoleImplTeleporter.cpp 24895 2009-11-24 12:30:13Z 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's a cancel.\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 hrc = pState->mptrConsole->powerDown();
772 pState->mptrProgress->notifyComplete(hrc);
773 }
774 else
775 {
776 /*
777 * Work the state machinery on failure.
778 *
779 * If the state is no longer 'Teleporting*', some other operation has
780 * canceled us and there is nothing we need to do here. In all other
781 * cases, we've failed one way or another.
782 */
783 if ( enmMachineState == MachineState_Teleporting
784 || enmMachineState == MachineState_TeleportingPausedVM
785 )
786 {
787 if (pState->mfUnlockedMedia)
788 {
789 ErrorInfoKeeper Oak;
790 HRESULT hrc2 = pState->mptrConsole->mControl->LockMedia();
791 if (FAILED(hrc2))
792 {
793 uint64_t StartMS = RTTimeMilliTS();
794 do
795 {
796 RTThreadSleep(2);
797 hrc2 = pState->mptrConsole->mControl->LockMedia();
798 } while ( FAILED(hrc2)
799 && RTTimeMilliTS() - StartMS < 2000);
800 }
801 if (SUCCEEDED(hrc2))
802 pState->mfUnlockedMedia = true;
803 else
804 LogRel(("FATAL ERROR: Failed to re-take the media locks. hrc2=%Rhrc\n", hrc2));
805 }
806
807 switch (enmVMState)
808 {
809 case VMSTATE_RUNNING:
810 case VMSTATE_RUNNING_LS:
811 case VMSTATE_DEBUGGING:
812 case VMSTATE_DEBUGGING_LS:
813 case VMSTATE_POWERING_OFF:
814 case VMSTATE_POWERING_OFF_LS:
815 case VMSTATE_RESETTING:
816 case VMSTATE_RESETTING_LS:
817 Assert(!pState->mfSuspendedByUs);
818 Assert(!pState->mfUnlockedMedia);
819 pState->mptrConsole->setMachineState(MachineState_Running);
820 break;
821
822 case VMSTATE_GURU_MEDITATION:
823 case VMSTATE_GURU_MEDITATION_LS:
824 pState->mptrConsole->setMachineState(MachineState_Stuck);
825 break;
826
827 case VMSTATE_FATAL_ERROR:
828 case VMSTATE_FATAL_ERROR_LS:
829 pState->mptrConsole->setMachineState(MachineState_Paused);
830 break;
831
832 default:
833 AssertMsgFailed(("%s\n", VMR3GetStateName(enmVMState)));
834 case VMSTATE_SUSPENDED:
835 case VMSTATE_SUSPENDED_LS:
836 case VMSTATE_SUSPENDING:
837 case VMSTATE_SUSPENDING_LS:
838 case VMSTATE_SUSPENDING_EXT_LS:
839 if (!pState->mfUnlockedMedia)
840 {
841 pState->mptrConsole->setMachineState(MachineState_Paused);
842 if (pState->mfSuspendedByUs)
843 {
844 autoLock.leave();
845 int rc = VMR3Resume(pState->mpVM);
846 AssertLogRelMsgRC(rc, ("VMR3Resume -> %Rrc\n", rc));
847 autoLock.enter();
848 }
849 }
850 else
851 {
852 /* Faking a guru meditation is the best I can think of doing here... */
853 pState->mptrConsole->setMachineState(MachineState_Stuck);
854 }
855 break;
856 }
857 }
858 }
859 autoLock.leave();
860
861 /*
862 * Cleanup.
863 */
864 Assert(pState->mhSocket == NIL_RTSOCKET);
865 delete pState;
866
867 return VINF_SUCCESS; /* ignored */
868}
869
870
871/**
872 * Start teleporter to the specified target.
873 *
874 * @returns COM status code.
875 *
876 * @param aHostname The name of the target host.
877 * @param aPort The TCP port number.
878 * @param aPassword The password.
879 * @param aMaxDowntime Max allowed "downtime" in milliseconds.
880 * @param aProgress Where to return the progress object.
881 */
882STDMETHODIMP
883Console::Teleport(IN_BSTR aHostname, ULONG aPort, IN_BSTR aPassword, ULONG aMaxDowntime, IProgress **aProgress)
884{
885 /*
886 * Validate parameters, check+hold object status, write lock the object
887 * and validate the state.
888 */
889 CheckComArgOutPointerValid(aProgress);
890 CheckComArgStrNotEmptyOrNull(aHostname);
891 CheckComArgNotNull(aHostname);
892 CheckComArgExprMsg(aPort, aPort > 0 && aPort <= 65535, ("is %u", aPort));
893 CheckComArgExprMsg(aMaxDowntime, aMaxDowntime > 0, ("is %u", aMaxDowntime));
894
895 AutoCaller autoCaller(this);
896 CheckComRCReturnRC(autoCaller.rc());
897
898 AutoWriteLock autoLock(this);
899 LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
900
901 switch (mMachineState)
902 {
903 case MachineState_Running:
904 case MachineState_Paused:
905 break;
906
907 default:
908 return setError(VBOX_E_INVALID_VM_STATE,
909 tr("Invalid machine state: %s (must be Running or Paused)"),
910 Global::stringifyMachineState(mMachineState));
911 }
912
913
914 /*
915 * Create a progress object, spawn a worker thread and change the state.
916 * Note! The thread won't start working until we release the lock.
917 */
918 LogFlowThisFunc(("Initiating TELEPORT request...\n"));
919
920 ComObjPtr<Progress> ptrProgress;
921 HRESULT hrc = ptrProgress.createObject();
922 CheckComRCReturnRC(hrc);
923 hrc = ptrProgress->init(static_cast<IConsole *>(this), Bstr(tr("Teleporter")), TRUE /*aCancelable*/);
924 CheckComRCReturnRC(hrc);
925
926 TeleporterStateSrc *pState = new TeleporterStateSrc(this, mpVM, ptrProgress, mMachineState);
927 pState->mstrPassword = aPassword;
928 pState->mstrHostname = aHostname;
929 pState->muPort = aPort;
930 pState->mcMsMaxDowntime = aMaxDowntime;
931
932 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
933 ptrProgress->setCancelCallback(teleporterProgressCancelCallback, pvUser);
934
935 int vrc = RTThreadCreate(NULL, Console::teleporterSrcThreadWrapper, (void *)pState, 0 /*cbStack*/,
936 RTTHREADTYPE_EMULATION, 0 /*fFlags*/, "Teleport");
937 if (RT_SUCCESS(vrc))
938 {
939 if (mMachineState == MachineState_Running)
940 hrc = setMachineState(MachineState_Teleporting);
941 else
942 hrc = setMachineState(MachineState_TeleportingPausedVM);
943 if (SUCCEEDED(hrc))
944 {
945 ptrProgress.queryInterfaceTo(aProgress);
946 mptrCancelableProgress = ptrProgress;
947 }
948 else
949 ptrProgress->Cancel();
950 }
951 else
952 {
953 ptrProgress->setCancelCallback(NULL, NULL);
954 delete pState;
955 hrc = setError(E_FAIL, tr("RTThreadCreate -> %Rrc"), vrc);
956 }
957
958 return hrc;
959}
960
961
962/**
963 * Creates a TCP server that listens for the source machine and passes control
964 * over to Console::teleporterTrgServeConnection().
965 *
966 * @returns VBox status code.
967 * @param pVM The VM handle
968 * @param pMachine The IMachine for the virtual machine.
969 * @param fStartPaused Whether to start it in the Paused (true) or
970 * Running (false) state,
971 * @param pProgress Pointer to the progress object.
972 *
973 * @remarks The caller expects error information to be set on failure.
974 * @todo Check that all the possible failure paths sets error info...
975 */
976int
977Console::teleporterTrg(PVM pVM, IMachine *pMachine, bool fStartPaused, Progress *pProgress)
978{
979 /*
980 * Get the config.
981 */
982 ULONG uPort;
983 HRESULT hrc = pMachine->COMGETTER(TeleporterPort)(&uPort);
984 if (FAILED(hrc))
985 return VERR_GENERAL_FAILURE;
986 ULONG const uPortOrg = uPort;
987
988 Bstr bstrAddress;
989 hrc = pMachine->COMGETTER(TeleporterAddress)(bstrAddress.asOutParam());
990 if (FAILED(hrc))
991 return VERR_GENERAL_FAILURE;
992 Utf8Str strAddress(bstrAddress);
993 const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str();
994
995 Bstr bstrPassword;
996 hrc = pMachine->COMGETTER(TeleporterPassword)(bstrPassword.asOutParam());
997 if (FAILED(hrc))
998 return VERR_GENERAL_FAILURE;
999 Utf8Str strPassword(bstrPassword);
1000 strPassword.append('\n'); /* To simplify password checking. */
1001
1002 /*
1003 * Create the TCP server.
1004 */
1005 int vrc;
1006 PRTTCPSERVER hServer;
1007 if (uPort)
1008 vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
1009 else
1010 {
1011 for (int cTries = 10240; cTries > 0; cTries--)
1012 {
1013 uPort = RTRandU32Ex(cTries >= 8192 ? 49152 : 1024, 65534);
1014 vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
1015 if (vrc != VERR_NET_ADDRESS_IN_USE)
1016 break;
1017 }
1018 if (RT_SUCCESS(vrc))
1019 {
1020 hrc = pMachine->COMSETTER(TeleporterPort)(uPort);
1021 if (FAILED(hrc))
1022 {
1023 RTTcpServerDestroy(hServer);
1024 return VERR_GENERAL_FAILURE;
1025 }
1026 }
1027 }
1028 if (RT_FAILURE(vrc))
1029 return vrc;
1030
1031 /*
1032 * Create a one-shot timer for timing out after 5 mins.
1033 */
1034 RTTIMERLR hTimerLR;
1035 vrc = RTTimerLRCreateEx(&hTimerLR, 0 /*ns*/, RTTIMER_FLAGS_CPU_ANY, teleporterDstTimeout, hServer);
1036 if (RT_SUCCESS(vrc))
1037 {
1038 vrc = RTTimerLRStart(hTimerLR, 5*60*UINT64_C(1000000000) /*ns*/);
1039 if (RT_SUCCESS(vrc))
1040 {
1041 /*
1042 * Do the job, when it returns we're done.
1043 */
1044 TeleporterStateTrg State(this, pVM, pProgress, pMachine, mControl, &hTimerLR, fStartPaused);
1045 State.mstrPassword = strPassword;
1046 State.mhServer = hServer;
1047
1048 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(&State));
1049 if (pProgress->setCancelCallback(teleporterProgressCancelCallback, pvUser))
1050 {
1051 LogRel(("Teleporter: Waiting for incoming VM...\n"));
1052 vrc = RTTcpServerListen(hServer, Console::teleporterTrgServeConnection, &State);
1053 pProgress->setCancelCallback(NULL, NULL);
1054
1055 bool fPowerOff = false;
1056 if (vrc == VERR_TCP_SERVER_STOP)
1057 {
1058 vrc = State.mRc;
1059 /* Power off the VM on failure unless the state callback
1060 already did that. */
1061 if (RT_FAILURE(vrc))
1062 {
1063 VMSTATE enmVMState = VMR3GetState(pVM);
1064 if ( enmVMState != VMSTATE_OFF
1065 && enmVMState != VMSTATE_POWERING_OFF)
1066 fPowerOff = true;
1067 }
1068 }
1069 else if (vrc == VERR_TCP_SERVER_SHUTDOWN)
1070 {
1071 BOOL fCancelled = TRUE;
1072 hrc = pProgress->COMGETTER(Canceled)(&fCancelled);
1073 if (FAILED(hrc) || fCancelled)
1074 {
1075 setError(E_FAIL, tr("Teleporting canceled"));
1076 vrc = VERR_SSM_CANCELLED;
1077 }
1078 else
1079 {
1080 setError(E_FAIL, tr("Teleporter timed out waiting for incoming connection"));
1081 vrc = VERR_TIMEOUT;
1082 }
1083 LogRel(("Teleporter: RTTcpServerListen aborted - %Rrc\n", vrc));
1084 fPowerOff = true;
1085 }
1086 else
1087 {
1088 LogRel(("Teleporter: Unexpected RTTcpServerListen rc: %Rrc\n", vrc));
1089 vrc = VERR_IPE_UNEXPECTED_STATUS;
1090 fPowerOff = true;
1091 }
1092
1093 if (fPowerOff)
1094 {
1095 int vrc2 = VMR3PowerOff(pVM);
1096 AssertRC(vrc2);
1097 }
1098 }
1099 else
1100 vrc = VERR_SSM_CANCELLED;
1101 }
1102
1103 RTTimerLRDestroy(hTimerLR);
1104 }
1105 RTTcpServerDestroy(hServer);
1106
1107 /*
1108 * If we change TeleporterPort above, set it back to it's original
1109 * value before returning.
1110 */
1111 if (uPortOrg != uPort)
1112 pMachine->COMSETTER(TeleporterPort)(uPortOrg);
1113
1114 return vrc;
1115}
1116
1117
1118/**
1119 * Unlock the media.
1120 *
1121 * This is used in error paths.
1122 *
1123 * @param pState The teleporter state.
1124 */
1125static void teleporterTrgUnlockMedia(TeleporterStateTrg *pState)
1126{
1127 if (pState->mfLockedMedia)
1128 {
1129 pState->mpControl->UnlockMedia();
1130 pState->mfLockedMedia = false;
1131 }
1132}
1133
1134
1135static int teleporterTcpWriteACK(TeleporterStateTrg *pState, bool fAutomaticUnlock = true)
1136{
1137 int rc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1);
1138 if (RT_FAILURE(rc))
1139 {
1140 LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", rc));
1141 if (fAutomaticUnlock)
1142 teleporterTrgUnlockMedia(pState);
1143 }
1144 RTTcpFlush(pState->mhSocket);
1145 return rc;
1146}
1147
1148
1149static int teleporterTcpWriteNACK(TeleporterStateTrg *pState, int32_t rc2)
1150{
1151 /*
1152 * Unlock media sending the NACK. That way the other doesn't have to spin
1153 * waiting to regain the locks.
1154 */
1155 teleporterTrgUnlockMedia(pState);
1156
1157 char szMsg[64];
1158 size_t cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d\n", rc2);
1159 int rc = RTTcpWrite(pState->mhSocket, szMsg, cch);
1160 if (RT_FAILURE(rc))
1161 LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, rc));
1162 RTTcpFlush(pState->mhSocket);
1163 return rc;
1164}
1165
1166
1167/**
1168 * @copydoc FNRTTCPSERVE
1169 *
1170 * @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP.
1171 */
1172/*static*/ DECLCALLBACK(int)
1173Console::teleporterTrgServeConnection(RTSOCKET Sock, void *pvUser)
1174{
1175 TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser;
1176 pState->mhSocket = Sock;
1177
1178 /*
1179 * Say hello.
1180 */
1181 int vrc = RTTcpWrite(Sock, g_szWelcome, sizeof(g_szWelcome) - 1);
1182 if (RT_FAILURE(vrc))
1183 {
1184 LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc));
1185 return VINF_SUCCESS;
1186 }
1187
1188 /*
1189 * Password (includes '\n', see teleporterTrg).
1190 */
1191 const char *pszPassword = pState->mstrPassword.c_str();
1192 unsigned off = 0;
1193 while (pszPassword[off])
1194 {
1195 char ch;
1196 vrc = RTTcpRead(Sock, &ch, sizeof(ch), NULL);
1197 if ( RT_FAILURE(vrc)
1198 || pszPassword[off] != ch)
1199 {
1200 if (RT_FAILURE(vrc))
1201 LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc));
1202 else
1203 LogRel(("Teleporter: Invalid password (off=%u)\n", off));
1204 teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE);
1205 return VINF_SUCCESS;
1206 }
1207 off++;
1208 }
1209 vrc = teleporterTcpWriteACK(pState);
1210 if (RT_FAILURE(vrc))
1211 return VINF_SUCCESS;
1212 LogRel(("Teleporter: Incoming VM!\n"));
1213
1214 /*
1215 * Stop the server and cancel the timeout timer.
1216 *
1217 * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
1218 * to it we must not return that value!
1219 */
1220 RTTcpServerShutdown(pState->mhServer);
1221 RTTimerLRDestroy(*pState->mphTimerLR);
1222 *pState->mphTimerLR = NIL_RTTIMERLR;
1223
1224 /*
1225 * Command processing loop.
1226 */
1227 bool fDone = false;
1228 for (;;)
1229 {
1230 char szCmd[128];
1231 vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd));
1232 if (RT_FAILURE(vrc))
1233 break;
1234
1235 if (!strcmp(szCmd, "load"))
1236 {
1237 vrc = teleporterTcpWriteACK(pState);
1238 if (RT_FAILURE(vrc))
1239 break;
1240
1241 pState->moffStream = 0;
1242 void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
1243 vrc = VMR3LoadFromStream(pState->mpVM, &g_teleporterTcpOps, pvUser,
1244 teleporterProgressCallback, pvUser);
1245 if (RT_FAILURE(vrc))
1246 {
1247 LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc));
1248 teleporterTcpWriteNACK(pState, vrc);
1249 break;
1250 }
1251
1252 /* The EOS might not have been read, make sure it is. */
1253 pState->mfStopReading = false;
1254 size_t cbRead;
1255 vrc = teleporterTcpOpRead(pvUser, pState->moffStream, szCmd, 1, &cbRead);
1256 if (vrc != VERR_EOF)
1257 {
1258 LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc));
1259 teleporterTcpWriteNACK(pState, vrc);
1260 break;
1261 }
1262
1263 vrc = teleporterTcpWriteACK(pState);
1264 }
1265 else if (!strcmp(szCmd, "cancel"))
1266 {
1267 /* Don't ACK this. */
1268 LogRel(("Teleporter: Received cancel command.\n"));
1269 vrc = VERR_SSM_CANCELLED;
1270 }
1271 else if (!strcmp(szCmd, "lock-media"))
1272 {
1273 HRESULT hrc = pState->mpControl->LockMedia();
1274 if (SUCCEEDED(hrc))
1275 {
1276 pState->mfLockedMedia = true;
1277 vrc = teleporterTcpWriteACK(pState);
1278 }
1279 else
1280 {
1281 vrc = VERR_FILE_LOCK_FAILED;
1282 teleporterTcpWriteNACK(pState, vrc);
1283 }
1284 }
1285 else if ( !strcmp(szCmd, "hand-over-resume")
1286 || !strcmp(szCmd, "hand-over-paused"))
1287 {
1288 /*
1289 * Point of no return.
1290 *
1291 * Note! Since we cannot tell whether a VMR3Resume failure is
1292 * destructive for the source or not, we have little choice
1293 * but to ACK it first and take any failures locally.
1294 *
1295 * Ideally, we should try resume it first and then ACK (or
1296 * NACK) the request since this would reduce latency and
1297 * make it possible to recover from some VMR3Resume failures.
1298 */
1299 if ( pState->mptrProgress->notifyPointOfNoReturn()
1300 && pState->mfLockedMedia)
1301 {
1302 vrc = teleporterTcpWriteACK(pState);
1303 if (RT_SUCCESS(vrc))
1304 {
1305 if (!strcmp(szCmd, "hand-over-resume"))
1306 vrc = VMR3Resume(pState->mpVM);
1307 else
1308 pState->mptrConsole->setMachineState(MachineState_Paused);
1309 fDone = true;
1310 break;
1311 }
1312 }
1313 else
1314 {
1315 vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED;
1316 teleporterTcpWriteNACK(pState, vrc);
1317 }
1318 }
1319 else
1320 {
1321 LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd));
1322 vrc = VERR_NOT_IMPLEMENTED;
1323 teleporterTcpWriteNACK(pState, vrc);
1324 }
1325
1326 if (RT_FAILURE(vrc))
1327 break;
1328 }
1329
1330 if (RT_SUCCESS(vrc) && !fDone)
1331 vrc = VERR_WRONG_ORDER;
1332 if (RT_FAILURE(vrc))
1333 teleporterTrgUnlockMedia(pState);
1334
1335 pState->mRc = vrc;
1336 pState->mhSocket = NIL_RTSOCKET;
1337 LogFlowFunc(("returns mRc=%Rrc\n", vrc));
1338 return VERR_TCP_SERVER_STOP;
1339}
1340
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