diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2014-03-26 19:21:20 +0000 |
---|---|---|
committer | <> | 2014-05-08 15:03:54 +0000 |
commit | fb123f93f9f5ce42c8e5785d2f8e0edaf951740e (patch) | |
tree | c2103d76aec5f1f10892cd1d3a38e24f665ae5db /src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp | |
parent | 58ed4748338f9466599adfc8a9171280ed99e23f (diff) | |
download | VirtualBox-master.tar.gz |
Imported from /home/lorry/working-area/delta_VirtualBox/VirtualBox-4.3.10.tar.bz2.HEADVirtualBox-4.3.10master
Diffstat (limited to 'src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp')
-rw-r--r-- | src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp | 2364 |
1 files changed, 2364 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp new file mode 100644 index 00000000..d96b13ad --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp @@ -0,0 +1,2364 @@ +/* $Id: VBoxServiceControlSession.cpp $ */ +/** @file + * VBoxServiceControlSession - Guest session handling. Also handles + * the forked session processes. + */ + +/* + * Copyright (C) 2013 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * 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 <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceControl.h" + +using namespace guestControl; + +/******************************************************************************* +* Externals * +*******************************************************************************/ +extern RTLISTANCHOR g_lstControlSessionThreads; +extern VBOXSERVICECTRLSESSION g_Session; + +extern int VBoxServiceLogCreate(const char *pszLogFile); +extern void VBoxServiceLogDestroy(void); + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static int gstcntlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile); +static int gstcntlSessionFileAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLFILE pFile); +static PVBOXSERVICECTRLFILE gstcntlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle); +static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser); +/* Host -> Guest handlers. */ +static int gstcntlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf); +static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandlePathRename(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcExec(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcInput(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf); +static int gstcntlSessionHandleProcOutput(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); + + +/** Generic option indices for session fork arguments. */ +enum +{ + VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */ +#ifdef DEBUG + VBOXSERVICESESSIONOPT_DUMP_STDOUT, + VBOXSERVICESESSIONOPT_DUMP_STDERR, +#endif + VBOXSERVICESESSIONOPT_LOG_FILE, + VBOXSERVICESESSIONOPT_USERNAME, + VBOXSERVICESESSIONOPT_SESSION_ID, + VBOXSERVICESESSIONOPT_SESSION_PROTO, + VBOXSERVICESESSIONOPT_THREAD_ID +}; + + +static int gstcntlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileClose(pFile->hFile); + if (RT_SUCCESS(rc)) + { + /* Remove file entry in any case. */ + RTListNodeRemove(&pFile->Node); + /* Destroy this object. */ + RTMemFree(pFile); + } + + return rc; +} + + +/** @todo No locking done yet! */ +static PVBOXSERVICECTRLFILE gstcntlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, + uint32_t uHandle) +{ + AssertPtrReturn(pSession, NULL); + + PVBOXSERVICECTRLFILE pFileCur = NULL; + /** @todo Use a map later! */ + RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node) + { + if (pFileCur->uHandle == uHandle) + return pFileCur; + } + + return NULL; +} + + +static int gstcntlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szDir[RTPATH_MAX]; + uint32_t uFlags = 0; + + int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, + /* Directory to remove. */ + szDir, sizeof(szDir), + /* Flags of type DIRREMOVE_FLAG_. */ + &uFlags); + if (RT_SUCCESS(rc)) + { + uint32_t uFlagsRemRec = 0; + bool fRecursive = false; + + if (!(uFlags & ~DIRREMOVE_FLAG_VALID_MASK)) + { + if (uFlags & DIRREMOVE_FLAG_RECURSIVE) + { + /* Note: DIRREMOVE_FLAG_RECURSIVE must be set explicitly. + * Play safe here. */ + fRecursive = true; + } + + if (uFlags & DIRREMOVE_FLAG_CONTENT_AND_DIR) + { + /* Setting direct value is intentional. */ + uFlagsRemRec = RTDIRRMREC_F_CONTENT_AND_DIR; + } + + if (uFlags & DIRREMOVE_FLAG_CONTENT_ONLY) + { + /* Setting direct value is intentional. */ + uFlagsRemRec |= RTDIRRMREC_F_CONTENT_ONLY; + } + } + else + rc = VERR_NOT_SUPPORTED; + + VBoxServiceVerbose(4, "[Dir %s]: Removing with uFlags=0x%x, fRecursive=%RTbool\n", + szDir, uFlags, fRecursive); + + if (RT_SUCCESS(rc)) + { + /** @todo Add own recursive function (or a new IPRT function w/ callback?) to + * provide guest-to-host progress reporting. */ + if (fRecursive) + rc = RTDirRemoveRecursive(szDir, uFlagsRemRec); + else + rc = RTDirRemove(szDir); + } + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", + szDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Removing directory \"%s\" returned rc=%Rrc\n", + szDir, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szFile[RTPATH_MAX]; + char szAccess[64]; + char szDisposition[64]; + char szSharing[64]; + uint32_t uCreationMode = 0; + uint64_t uOffset = 0; + uint32_t uHandle = 0; + + int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx, + /* File to open. */ + szFile, sizeof(szFile), + /* Open mode. */ + szAccess, sizeof(szAccess), + /* Disposition. */ + szDisposition, sizeof(szDisposition), + /* Sharing. */ + szSharing, sizeof(szSharing), + /* Creation mode. */ + &uCreationMode, + /* Offset. */ + &uOffset); + VBoxServiceVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, uOffset=%RU64, rc=%Rrc\n", + szFile, szAccess, szDisposition, szSharing, uOffset, rc); + + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE)); + if (pFile) + { + if (!strlen(szFile)) + rc = VERR_INVALID_PARAMETER; + + if ( RT_SUCCESS(rc) + && !RTStrPrintf(pFile->szName, sizeof(pFile->szName), "%s", szFile)) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + uint64_t fFlags; + rc = RTFileModeToFlagsEx(szAccess, szDisposition, + NULL /* pszSharing, not used yet */, &fFlags); + VBoxServiceVerbose(4, "[File %s]: Opening flags=0x%x, rc=%Rrc\n", + pFile->szName, fFlags, rc); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(&pFile->hFile, pFile->szName, fFlags); + if ( RT_SUCCESS(rc) + && uOffset) + { + /* Seeking is optional. However, the whole operation + * will fail if we don't succeed seeking to the wanted position. */ + rc = RTFileSeek(pFile->hFile, (int64_t)uOffset, RTFILE_SEEK_BEGIN, NULL /* Current offset */); + if (RT_FAILURE(rc)) + VBoxServiceError("[File %s]: Seeking to offset %RU64 failed; rc=%Rrc\n", + pFile->szName, uOffset, rc); + } + else if (RT_FAILURE(rc)) + VBoxServiceError("[File %s]: Opening failed; rc=%Rrc\n", + pFile->szName, rc); + } + + if (RT_SUCCESS(rc)) + { + uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID); + pFile->uHandle = uHandle; + + /* rc = */ RTListAppend(&pSession->lstFiles, &pFile->Node); + + VBoxServiceVerbose(3, "[File %s]: Opened (ID=%RU32)\n", + pFile->szName, pFile->uHandle); + } + + if (RT_FAILURE(rc)) + { + if (pFile->hFile) + RTFileClose(pFile->hFile); + RTMemFree(pFile); + } + } + else + rc = VERR_NO_MEMORY; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle); + if (RT_FAILURE(rc2)) + VBoxServiceError("[File %s]: Failed to report file open status, rc=%Rrc\n", + szFile, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Opening file \"%s\" (open mode=\"%s\", disposition=\"%s\", creation mode=0x%x returned rc=%Rrc\n", + szFile, szAccess, szDisposition, uCreationMode, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = gstcntlSessionFileDestroy(pFile); + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file close status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Closing file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToRead; + int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead); + if (RT_SUCCESS(rc)) + { + void *pvDataRead = pvScratchBuf; + size_t cbRead = 0; + + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (cbToRead) + { + if (cbToRead > cbScratchBuf) + { + pvDataRead = RTMemAlloc(cbToRead); + if (!pvDataRead) + rc = VERR_NO_MEMORY; + } + + if (RT_LIKELY(RT_SUCCESS(rc))) + rc = RTFileRead(pFile->hFile, pvDataRead, cbToRead, &cbRead); + } + else + rc = VERR_BUFFER_UNDERFLOW; + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead); + if ( cbToRead > cbScratchBuf + && pvDataRead) + RTMemFree(pvDataRead); + + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Reading file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToRead; int64_t iOffset; + + int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, + &uHandle, &cbToRead, (uint64_t *)&iOffset); + if (RT_SUCCESS(rc)) + { + void *pvDataRead = pvScratchBuf; + size_t cbRead = 0; + + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (cbToRead) + { + if (cbToRead > cbScratchBuf) + { + pvDataRead = RTMemAlloc(cbToRead); + if (!pvDataRead) + rc = VERR_NO_MEMORY; + } + + if (RT_LIKELY(RT_SUCCESS(rc))) + rc = RTFileReadAt(pFile->hFile, iOffset, pvDataRead, cbToRead, &cbRead); + } + else + rc = VERR_BUFFER_UNDERFLOW; + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead); + if ( cbToRead > cbScratchBuf + && pvDataRead) + RTMemFree(pvDataRead); + + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Reading file \"%s\" at offset (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToWrite; + + int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, + pvScratchBuf, cbScratchBuf, + &cbToWrite); + if (RT_SUCCESS(rc)) + { + size_t cbWritten = 0; + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWrite(pFile->hFile, pvScratchBuf, cbToWrite, &cbWritten); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Writing pvScratchBuf=%p, cbToWrite=%RU32, cbWritten=%zu, rc=%Rrc\n", + pFile->szName, pvScratchBuf, cbToWrite, cbWritten, rc); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Writing file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToWrite; int64_t iOffset; + + int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, + pvScratchBuf, cbScratchBuf, + &cbToWrite, (uint64_t *)&iOffset); + if (RT_SUCCESS(rc)) + { + size_t cbWritten = 0; + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWriteAt(pFile->hFile, iOffset, + pvScratchBuf, cbToWrite, &cbWritten); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Writing iOffset=%RI64, pvScratchBuf=%p, cbToWrite=%RU32, cbWritten=%zu, rc=%Rrc\n", + pFile->szName, iOffset, pvScratchBuf, cbToWrite, cbWritten, rc); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Writing file \"%s\" at offset (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t uSeekMethod; + uint64_t uOffset; /* Will be converted to int64_t. */ + + uint64_t uOffsetActual = 0; + + int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, + &uSeekMethod, &uOffset); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + unsigned uSeekMethodIPRT; + switch (uSeekMethod) + { + case GUEST_FILE_SEEKTYPE_BEGIN: + uSeekMethodIPRT = RTFILE_SEEK_BEGIN; + break; + + case GUEST_FILE_SEEKTYPE_CURRENT: + uSeekMethodIPRT = RTFILE_SEEK_CURRENT; + break; + + case GUEST_FILE_SEEKTYPE_END: + uSeekMethodIPRT = RTFILE_SEEK_END; + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + rc = RTFileSeek(pFile->hFile, (int64_t)uOffset, + uSeekMethodIPRT, &uOffsetActual); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Seeking to iOffset=%RI64, uSeekMethodIPRT=%RU16, rc=%Rrc\n", + pFile->szName, (int64_t)uOffset, uSeekMethodIPRT, rc); +#endif + } + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, uOffsetActual); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file seek status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Seeking file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint64_t uOffsetActual = 0; + + int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + uOffsetActual = RTFileTell(pFile->hFile); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Telling uOffsetActual=%RU64\n", + pFile->szName, uOffsetActual); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, uOffsetActual); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Telling file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szSource[RTPATH_MAX]; + char szDest[RTPATH_MAX]; + uint32_t uFlags = 0; + + int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, + szSource, sizeof(szSource), + szDest, sizeof(szDest), + /* Flags of type PATHRENAME_FLAG_. */ + &uFlags); + if (RT_SUCCESS(rc)) + { + if (uFlags & ~PATHRENAME_FLAG_VALID_MASK) + rc = VERR_NOT_SUPPORTED; + + VBoxServiceVerbose(4, "Renaming \"%s\" to \"%s\", uFlags=0x%x, rc=%Rrc\n", + szSource, szDest, uFlags, rc); + + if (RT_SUCCESS(rc)) + { + if (uFlags & PATHRENAME_FLAG_NO_REPLACE) + uFlags |= RTPATHRENAME_FLAGS_NO_REPLACE; + + if (uFlags & PATHRENAME_FLAG_REPLACE) + uFlags |= RTPATHRENAME_FLAGS_REPLACE; + + if (uFlags & PATHRENAME_FLAG_NO_SYMLINKS) + uFlags |= RTPATHRENAME_FLAGS_NO_SYMLINKS; + + rc = RTPathRename(szSource, szDest, uFlags); + } + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report renaming status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Renaming \"%s\" to \"%s\" returned rc=%Rrc\n", + szSource, szDest, rc); +#endif + return rc; +} + + +/** + * Handles starting a guest processes. + * + * @returns IPRT status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +int gstcntlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ + + switch (pHostCtx->uProtocol) + { + case 1: /* Guest Additions < 4.3. */ + if (pHostCtx->uNumParms != 11) + rc = VERR_NOT_SUPPORTED; + break; + + case 2: /* Guest Additions >= 4.3. */ + if (pHostCtx->uNumParms != 12) + rc = VERR_NOT_SUPPORTED; + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + VBOXSERVICECTRLPROCSTARTUPINFO startupInfo; + RT_ZERO(startupInfo); + + /* Initialize maximum environment block size -- needed as input + * parameter to retrieve the stuff from the host. On output this then + * will contain the actual block size. */ + startupInfo.cbEnv = sizeof(startupInfo.szEnv); + + rc = VbglR3GuestCtrlProcGetStart(pHostCtx, + /* Command */ + startupInfo.szCmd, sizeof(startupInfo.szCmd), + /* Flags */ + &startupInfo.uFlags, + /* Arguments */ + startupInfo.szArgs, sizeof(startupInfo.szArgs), &startupInfo.uNumArgs, + /* Environment */ + startupInfo.szEnv, &startupInfo.cbEnv, &startupInfo.uNumEnvVars, + /* Credentials; for hosts with VBox < 4.3 (protocol version 1). + * For protocl v2 and up the credentials are part of the session + * opening call. */ + startupInfo.szUser, sizeof(startupInfo.szUser), + startupInfo.szPassword, sizeof(startupInfo.szPassword), + /* Timeout (in ms) */ + &startupInfo.uTimeLimitMS, + /* Process priority */ + &startupInfo.uPriority, + /* Process affinity */ + startupInfo.uAffinity, sizeof(startupInfo.uAffinity), &startupInfo.uNumAffinity); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Request to start process szCmd=%s, uFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n", + startupInfo.szCmd, startupInfo.uFlags, + startupInfo.uNumArgs ? startupInfo.szArgs : "<None>", + startupInfo.uNumEnvVars ? startupInfo.szEnv : "<None>", + startupInfo.uTimeLimitMS); + + rc = GstCntlSessionProcessStartAllowed(pSession, &fStartAllowed); + if (RT_SUCCESS(rc)) + { + if (fStartAllowed) + { + rc = GstCntlProcessStart(pSession, &startupInfo, pHostCtx->uContextID); + } + else + rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */ + } + } + } + + /* In case of an error we need to notify the host to not wait forever for our response. */ + if (RT_FAILURE(rc)) + { + VBoxServiceError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n", + rc, pHostCtx->uProtocol, pHostCtx->uNumParms); + + /* Don't report back if we didn't supply sufficient buffer for getting + * the actual command -- we don't have the matching context ID. */ + if (rc != VERR_TOO_MUCH_DATA) + { + /* + * Note: The context ID can be 0 because we mabye weren't able to fetch the command + * from the host. The host in case has to deal with that! + */ + int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /* PID, invalid */, + PROC_STS_ERROR, rc, + NULL /* pvData */, 0 /* cbData */); + if (RT_FAILURE(rc2)) + VBoxServiceError("Error sending start process status to host, rc=%Rrc\n", rc2); + } + } + + return rc; +} + + +/** + * Sends stdin input to a specific guest process. + * + * @returns IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + * @param pvScratchBuf The scratch buffer. + * @param cbScratchBuf The scratch buffer size for retrieving the input data. + */ +int gstcntlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uFlags; + uint32_t cbSize; + + uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */ + uint32_t cbWritten = 0; /* Number of bytes written to the guest. */ + + /* + * Ask the host for the input data. + */ + int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &uFlags, + pvScratchBuf, cbScratchBuf, &cbSize); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Failed to retrieve process input command for PID=%RU32, rc=%Rrc\n", + uPID, rc); + } + else if (cbSize > cbScratchBuf) + { + VBoxServiceError("Too much process input received, rejecting: uPID=%RU32, cbSize=%RU32, cbScratchBuf=%RU32\n", + uPID, cbSize, cbScratchBuf); + rc = VERR_TOO_MUCH_DATA; + } + else + { + /* + * Is this the last input block we need to deliver? Then let the pipe know ... + */ + bool fPendingClose = false; + if (uFlags & INPUT_FLAG_EOF) + { + fPendingClose = true; +#ifdef DEBUG + VBoxServiceVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", + uPID, cbSize); +#endif + } + + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleInput(pProcess, pHostCtx, fPendingClose, + pvScratchBuf, cbSize); + if (RT_FAILURE(rc)) + VBoxServiceError("Error handling input command for PID=%RU32, rc=%Rrc\n", + uPID, rc); + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Setting input for PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +/** + * Gets stdout/stderr output of a specific guest process. + * + * @return IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +int gstcntlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uHandleID; + uint32_t uFlags; + + int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &uFlags); +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, uFlags=%RU32\n", + uPID, pHostCtx->uContextID, uHandleID, uFlags); +#endif + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleOutput(pProcess, pHostCtx, + uHandleID, _64K /* cbToRead */, uFlags); + if (RT_FAILURE(rc)) + VBoxServiceError("Error getting output for PID=%RU32, rc=%Rrc\n", + uPID, rc); + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +/** + * Tells a guest process to terminate. + * + * @return IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +int gstcntlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleTerm(pProcess); + + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +int gstcntlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uWaitFlags; uint32_t uTimeoutMS; + + int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VERR_NOT_IMPLEMENTED; /** @todo */ + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + + return rc; +} + + +int GstCntlSessionHandler(PVBOXSERVICECTRLSESSION pSession, + uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf, + volatile bool *pfShutdown) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + /** + * Only anonymous sessions (that is, sessions which run with local + * service privileges) or forked session processes can do certain + * operations. + */ + bool fImpersonated = ( pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK + || pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS); + + switch (uMsg) + { + case HOST_SESSION_CLOSE: + /* Shutdown (this fork). */ + rc = GstCntlSessionClose(pSession); + *pfShutdown = true; /* Shutdown in any case. */ + break; + + case HOST_DIR_REMOVE: + rc = fImpersonated + ? gstcntlSessionHandleDirRemove(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_EXEC_CMD: + rc = gstcntlSessionHandleProcExec(pSession, pHostCtx); + break; + + case HOST_EXEC_SET_INPUT: + rc = gstcntlSessionHandleProcInput(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf); + break; + + case HOST_EXEC_GET_OUTPUT: + rc = gstcntlSessionHandleProcOutput(pSession, pHostCtx); + break; + + case HOST_EXEC_TERMINATE: + rc = gstcntlSessionHandleProcTerminate(pSession, pHostCtx); + break; + + case HOST_EXEC_WAIT_FOR: + rc = gstcntlSessionHandleProcWaitFor(pSession, pHostCtx); + break; + + case HOST_FILE_OPEN: + rc = fImpersonated + ? gstcntlSessionHandleFileOpen(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_CLOSE: + rc = fImpersonated + ? gstcntlSessionHandleFileClose(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_READ: + rc = fImpersonated + ? gstcntlSessionHandleFileRead(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_READ_AT: + rc = fImpersonated + ? gstcntlSessionHandleFileReadAt(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_WRITE: + rc = fImpersonated + ? gstcntlSessionHandleFileWrite(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_WRITE_AT: + rc = fImpersonated + ? gstcntlSessionHandleFileWriteAt(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_SEEK: + rc = fImpersonated + ? gstcntlSessionHandleFileSeek(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_TELL: + rc = fImpersonated + ? gstcntlSessionHandleFileTell(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_PATH_RENAME: + rc = fImpersonated + ? gstcntlSessionHandlePathRename(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + default: + rc = VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID); + VBoxServiceVerbose(3, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", + uMsg, pHostCtx->uNumParms); + break; + } + + if (RT_FAILURE(rc)) + VBoxServiceError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", + uMsg, pHostCtx->uNumParms, rc); + + return rc; +} + + +/** + * Thread main routine for a forked guest session process. + * This thread runs in the main executable to control the forked + * session process. + * + * @return IPRT status code. + * @param RTTHREAD Pointer to the thread's data. + * @param void* User-supplied argument pointer. + * + */ +static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser) +{ + PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser; + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + uint32_t uSessionID = pThread->StartupInfo.uSessionID; + + uint32_t uClientID; + int rc = VbglR3GuestCtrlConnect(&uClientID); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Session ID=%RU32 thread running, client ID=%RU32\n", + uSessionID, uClientID); + + /* The session thread is not interested in receiving any commands; + * tell the host service. */ + rc = VbglR3GuestCtrlMsgFilterSet(uClientID, 0 /* Skip all */, + 0 /* Filter mask to add */, 0 /* Filter mask to remove */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Unable to set message filter, rc=%Rrc\n", rc); + /* Non-critical. */ + rc = VINF_SUCCESS; + } + } + else + VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + pThread->fShutdown = true; + + /* Let caller know that we're done initializing, regardless of the result. */ + int rc2 = RTThreadUserSignal(RTThreadSelf()); + AssertRC(rc2); + + if (RT_FAILURE(rc)) + return rc; + + bool fProcessAlive = true; + RTPROCSTATUS ProcessStatus; + RT_ZERO(ProcessStatus); + + int rcWait; + if (RT_SUCCESS(rc)) + { + uint32_t uTimeoutsMS = 30 * 1000; /** @todo Make this configurable. Later. */ + uint64_t u64TimeoutStart = 0; + + for (;;) + { + rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, + &ProcessStatus); + if (RT_UNLIKELY(rcWait == VERR_INTERRUPTED)) + continue; + else if ( rcWait == VINF_SUCCESS + || rcWait == VERR_PROCESS_NOT_FOUND) + { + fProcessAlive = false; + break; + } + else + AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING, + ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait)); + + if (ASMAtomicReadBool(&pThread->fShutdown)) + { + if (!u64TimeoutStart) + { + VBoxServiceVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n", + pThread->hProcess, uSessionID); + + VBGLR3GUESTCTRLCMDCTX hostCtx = { uClientID, + VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSessionID), + pThread->StartupInfo.uProtocol, 2 /* uNumParms */ }; + rc = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* uFlags */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n", + pThread->hProcess, uSessionID, rc); + + if (rc == VERR_NOT_SUPPORTED) + { + /* Terminate guest session process in case it's not supported by a too old host. */ + rc = RTProcTerminate(pThread->hProcess); + VBoxServiceVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n", + pThread->hProcess, rc); + } + break; + } + + VBoxServiceVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32ms timeout) ...\n", + uSessionID, uTimeoutsMS); + u64TimeoutStart = RTTimeMilliTS(); + + continue; /* Don't waste time on waiting. */ + } + if (RTTimeMilliTS() - u64TimeoutStart > uTimeoutsMS) + { + VBoxServiceVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", + uSessionID); + break; + } + } + + RTThreadSleep(100); /* Wait a bit. */ + } + + if (!fProcessAlive) + { + VBoxServiceVerbose(2, "Guest session ID=%RU32 process terminated with rc=%Rrc, reason=%ld, status=%d\n", + uSessionID, rcWait, + ProcessStatus.enmReason, ProcessStatus.iStatus); + } + } + + uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED; + uint32_t uSessionRc = VINF_SUCCESS; /** uint32_t vs. int. */ + + if (fProcessAlive) + { + for (int i = 0; i < 3; i++) + { + VBoxServiceVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", + uSessionID, i + 1); + + rc = RTProcTerminate(pThread->hProcess); + if (RT_SUCCESS(rc)) + break; + RTThreadSleep(3000); + } + + VBoxServiceVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", + uSessionID, rc); + + uSessionStatus = RT_SUCCESS(rc) + ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA; + } + else + { + if (RT_SUCCESS(rcWait)) + { + switch (ProcessStatus.enmReason) + { + case RTPROCEXITREASON_NORMAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + break; + + case RTPROCEXITREASON_ABEND: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + break; + + case RTPROCEXITREASON_SIGNAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES; + break; + + default: + AssertMsgFailed(("Unhandled process termination reason (%ld)\n", + ProcessStatus.enmReason)); + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + break; + } + } + else + { + /* If we didn't find the guest process anymore, just assume it + * terminated normally. */ + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + } + } + + VBoxServiceVerbose(3, "Guest session ID=%RU32 thread ended with sessionStatus=%RU32, sessionRc=%Rrc\n", + uSessionID, uSessionStatus, uSessionRc); + + /* Report final status. */ + Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED); + VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSessionID) }; + rc2 = VbglR3GuestCtrlSessionNotify(&ctx, + uSessionStatus, uSessionRc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Reporting session ID=%RU32 final status failed with rc=%Rrc\n", + uSessionID, rc2); + + VbglR3GuestCtrlDisconnect(uClientID); + + VBoxServiceVerbose(3, "Session ID=%RU32 thread ended with rc=%Rrc\n", + uSessionID, rc); + return rc; +} + + +RTEXITCODE gstcntlSessionForkWorker(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, RTEXITCODE_FAILURE); + + bool fSessionFilter = true; + + VBoxServiceVerbose(0, "Hi, this is guest session ID=%RU32\n", + pSession->StartupInfo.uSessionID); + + uint32_t uClientID; + int rc = VbglR3GuestCtrlConnect(&uClientID); + if (RT_SUCCESS(rc)) + { + /* Set session filter. This prevents the guest control + * host service to send messages which belong to another + * session we don't want to handle. */ + uint32_t uFilterAdd = + VBOX_GUESTCTRL_FILTER_BY_SESSION(pSession->StartupInfo.uSessionID); + rc = VbglR3GuestCtrlMsgFilterSet(uClientID, + VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID), + uFilterAdd, 0 /* Filter remove */); + VBoxServiceVerbose(3, "Setting message filterAdd=0x%x returned %Rrc\n", + uFilterAdd, rc); + + if ( RT_FAILURE(rc) + && rc == VERR_NOT_SUPPORTED) + { + /* No session filter available. Skip. */ + fSessionFilter = false; + + rc = VINF_SUCCESS; + } + + VBoxServiceVerbose(1, "Using client ID=%RU32\n", uClientID); + } + else + VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc); + + /* Report started status. */ + VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID) }; + int rc2 = VbglR3GuestCtrlSessionNotify(&ctx, + GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", + pSession->StartupInfo.uSessionID, rc2); + + /* + * If session status cannot be posted to the host for + * some reason, bail out. + */ + if (RT_SUCCESS(rc)) + rc = rc2; + } + + /* Allocate a scratch buffer for commands which also send + * payload data with them. */ + uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */ + AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), RTEXITCODE_FAILURE); + uint8_t *pvScratchBuf = NULL; + + if (RT_SUCCESS(rc)) + { + pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf); + if (!pvScratchBuf) + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + bool fShutdown = false; + + VBGLR3GUESTCTRLCMDCTX ctxHost = { uClientID, 0 /* Context ID, zeroed */, + pSession->StartupInfo.uProtocol }; + for (;;) + { + VBoxServiceVerbose(3, "Waiting for host msg ...\n"); + uint32_t uMsg = 0; + uint32_t cParms = 0; + rc = VbglR3GuestCtrlMsgWaitFor(uClientID, &uMsg, &cParms); + if (rc == VERR_TOO_MUCH_DATA) + { +#ifdef DEBUG + VBoxServiceVerbose(4, "Message requires %RU32 parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms); +#endif + rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */ + } + else if (RT_FAILURE(rc)) + VBoxServiceVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved\n", uMsg, cParms); + + /* Set number of parameters for current host context. */ + ctxHost.uNumParms = cParms; + + /* ... and pass it on to the session handler. */ + rc = GstCntlSessionHandler(pSession, uMsg, &ctxHost, + pvScratchBuf, cbScratchBuf, &fShutdown); + } + + if (fShutdown) + break; + + /* Let's sleep for a bit and let others run ... */ + RTThreadYield(); + } + } + + VBoxServiceVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID); + + if (pvScratchBuf) + RTMemFree(pvScratchBuf); + + if (uClientID) + { + VBoxServiceVerbose(3, "Disconnecting client ID=%RU32 ...\n", uClientID); + VbglR3GuestCtrlDisconnect(uClientID); + } + + VBoxServiceVerbose(3, "Session worker returned with rc=%Rrc\n", rc); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Finds a (formerly) started guest process given by its PID and increases + * its reference count. Must be decreased by the caller with GstCntlProcessRelease(). + * Note: This does *not lock the process! + * + * @return PVBOXSERVICECTRLTHREAD Guest process if found, otherwise NULL. + * @param PVBOXSERVICECTRLSESSION Pointer to guest session where to search process in. + * @param uPID PID to search for. + */ +PVBOXSERVICECTRLPROCESS GstCntlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID) +{ + AssertPtrReturn(pSession, NULL); + + PVBOXSERVICECTRLPROCESS pProcess = NULL; + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pCurProcess; + RTListForEach(&pSession->lstProcesses, pCurProcess, VBOXSERVICECTRLPROCESS, Node) + { + if (pCurProcess->uPID == uPID) + { + rc = RTCritSectEnter(&pCurProcess->CritSect); + if (RT_SUCCESS(rc)) + { + pCurProcess->cRefs++; + rc = RTCritSectLeave(&pCurProcess->CritSect); + AssertRC(rc); + } + + if (RT_SUCCESS(rc)) + pProcess = pCurProcess; + break; + } + } + + rc = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc); + } + + return pProcess; +} + + +int GstCntlSessionClose(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VBoxServiceVerbose(0, "Session %RU32 is about to close ...\n", + pSession->StartupInfo.uSessionID); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Close all guest processes. + */ + VBoxServiceVerbose(0, "Stopping all guest processes ...\n"); + + /* Signal all guest processes in the active list that we want to shutdown. */ + size_t cProcesses = 0; + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + { + GstCntlProcessStop(pProcess); + cProcesses++; + } + + VBoxServiceVerbose(1, "%zu guest processes were signalled to stop\n", cProcesses); + + /* Wait for all active threads to shutdown and destroy the active thread list. */ + pProcess = RTListGetFirst(&pSession->lstProcesses, VBOXSERVICECTRLPROCESS, Node); + while (pProcess) + { + PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pProcess->Node, VBOXSERVICECTRLPROCESS, Node); + bool fLast = RTListNodeIsLast(&pSession->lstProcesses, &pProcess->Node); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + rc2 = GstCntlProcessWait(pProcess, + 30 * 1000 /* Wait 30 seconds max. */, + NULL /* rc */); + + int rc3 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + GstCntlProcessFree(pProcess); + + if (fLast) + break; + + pProcess = pNext; + } + +#ifdef DEBUG + pProcess = RTListGetFirst(&pSession->lstProcesses, VBOXSERVICECTRLPROCESS, Node); + while (pProcess) + { + PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pProcess->Node, VBOXSERVICECTRLPROCESS, Node); + bool fLast = RTListNodeIsLast(&pSession->lstProcesses, &pProcess->Node); + + VBoxServiceVerbose(1, "Process %p (PID %RU32) still in list\n", + pProcess, pProcess->uPID); + if (fLast) + break; + + pProcess = pNext; + } +#endif + AssertMsg(RTListIsEmpty(&pSession->lstProcesses), + ("Guest process list still contains entries when it should not\n")); + + /* + * Close all left guest files. + */ + VBoxServiceVerbose(0, "Closing all guest files ...\n"); + + PVBOXSERVICECTRLFILE pFile; + pFile = RTListGetFirst(&pSession->lstFiles, VBOXSERVICECTRLFILE, Node); + while (pFile) + { + PVBOXSERVICECTRLFILE pNext = RTListNodeGetNext(&pFile->Node, VBOXSERVICECTRLFILE, Node); + bool fLast = RTListNodeIsLast(&pSession->lstFiles, &pFile->Node); + + int rc2 = gstcntlSessionFileDestroy(pFile); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("Unable to close file \"%s\"; rc=%Rrc\n", + pFile->szName, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + if (fLast) + break; + + pFile = pNext; + } + + AssertMsg(RTListIsEmpty(&pSession->lstFiles), + ("Guest file list still contains entries when it should not\n")); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +int GstCntlSessionDestroy(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc = GstCntlSessionClose(pSession); + + /* Destroy critical section. */ + RTCritSectDelete(&pSession->CritSect); + + return rc; +} + + +int GstCntlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + RTListInit(&pSession->lstProcesses); + RTListInit(&pSession->lstFiles); + + pSession->uFlags = uFlags; + + /* Init critical section for protecting the thread lists. */ + int rc = RTCritSectInit(&pSession->CritSect); + AssertRC(rc); + + return rc; +} + + +/** + * Adds a guest process to a session's process list. + * + * @return IPRT status code. + * @param pSession Guest session to add process to. + * @param pProcess Guest process to add. + */ +int GstCntlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, + PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", + pProcess->uPID, pSession->StartupInfo.uSessionID); + + /* Add process to session list. */ + /* rc = */ RTListAppend(&pSession->lstProcesses, &pProcess->Node); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return VINF_SUCCESS; +} + + +/** + * Removes a guest process from a session's process list. + * + * @return IPRT status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +int GstCntlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, + PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", + pProcess->uPID, pSession->StartupInfo.uSessionID); + Assert(pProcess->cRefs == 0); + + RTListNodeRemove(&pProcess->Node); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return VINF_SUCCESS; +} + + +/** + * Determines whether starting a new guest process according to the + * maximum number of concurrent guest processes defined is allowed or not. + * + * @return IPRT status code. + * @param pbAllowed True if starting (another) guest process + * is allowed, false if not. + */ +int GstCntlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, + bool *pbAllowed) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pbAllowed, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check if we're respecting our memory policy by checking + * how many guest processes are started and served already. + */ + bool fLimitReached = false; + if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */ + { + uint32_t uProcsRunning = 0; + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + uProcsRunning++; + + VBoxServiceVerbose(3, "Maximum served guest processes set to %u, running=%u\n", + pSession->uProcsMaxKept, uProcsRunning); + + int32_t iProcsLeft = (pSession->uProcsMaxKept - uProcsRunning - 1); + if (iProcsLeft < 0) + { + VBoxServiceVerbose(3, "Maximum running guest processes reached (%u)\n", + pSession->uProcsMaxKept); + fLimitReached = true; + } + } + + *pbAllowed = !fLimitReached; + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/** + * Creates a guest session. This will spawn a new VBoxService.exe instance under + * behalf of the given user which then will act as a session host. On successful + * open, the session will be added to the given session thread list. + * + * @return IPRT status code. + * @param pList Which list to use to store the session thread in. + * @param pSessionStartupInfo Session startup info. + * @param ppSessionThread Returns newly created session thread on success. + * Optional. + */ +int GstCntlSessionThreadCreate(PRTLISTANCHOR pList, + const PVBOXSERVICECTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER); + /* ppSessionThread is optional. */ + +#ifdef DEBUG + PVBOXSERVICECTRLSESSIONTHREAD pSessionCur; + /* Check for existing session in debug mode. Should never happen because of + * Main consistency. */ + RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + if (pSessionCur->StartupInfo.uSessionID == pSessionStartupInfo->uSessionID) + { + AssertMsgFailed(("Guest session thread ID=%RU32 (%p) already exists when it should not\n", + pSessionCur->StartupInfo.uSessionID, pSessionCur)); + return VERR_ALREADY_EXISTS; + } + } +#endif + int rc = VINF_SUCCESS; + + /* Static counter to help tracking session thread <-> process relations. */ + static uint32_t s_uCtrlSessionThread = 0; + if (s_uCtrlSessionThread++ == UINT32_MAX) + s_uCtrlSessionThread = 0; /* Wrap around to not let IPRT freak out. */ + + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = + (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(VBOXSERVICECTRLSESSIONTHREAD)); + if (pSessionThread) + { + /* Copy over session startup info. */ + memcpy(&pSessionThread->StartupInfo, pSessionStartupInfo, + sizeof(VBOXSERVICECTRLSESSIONSTARTUPINFO)); + + pSessionThread->fShutdown = false; + pSessionThread->fStarted = false; + pSessionThread->fStopped = false; + + /* Is this an anonymous session? */ + /* Anonymous sessions run with the same privileges as the main VBoxService executable. */ + bool fAnonymous = !RT_BOOL(strlen(pSessionThread->StartupInfo.szUser)); + if (fAnonymous) + { + Assert(!strlen(pSessionThread->StartupInfo.szPassword)); + Assert(!strlen(pSessionThread->StartupInfo.szDomain)); + + VBoxServiceVerbose(3, "New anonymous guest session ID=%RU32 created, uFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->uFlags, + pSessionStartupInfo->uProtocol); + } + else + { + VBoxServiceVerbose(3, "Forking new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, uFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->szUser, +#ifdef DEBUG + pSessionStartupInfo->szPassword, +#else + "XXX", /* Never show passwords in release mode. */ +#endif + pSessionStartupInfo->szDomain, + pSessionStartupInfo->uFlags, + pSessionStartupInfo->uProtocol); + } + + rc = RTCritSectInit(&pSessionThread->CritSect); + AssertRC(rc); + + /* Fork child doing the actual session handling. */ + char szExeName[RTPATH_MAX]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + if (pszExeName) + { + char szParmUserName[GUESTPROCESS_MAX_USER_LEN + 32]; + if (!fAnonymous) + { + if (!RTStrPrintf(szParmUserName, sizeof(szParmUserName), "--user=%s", pSessionThread->StartupInfo.szUser)) + rc = VERR_BUFFER_OVERFLOW; + } + char szParmSessionID[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", + pSessionThread->StartupInfo.uSessionID)) + { + rc = VERR_BUFFER_OVERFLOW; + } + char szParmSessionProto[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32", + pSessionThread->StartupInfo.uProtocol)) + { + rc = VERR_BUFFER_OVERFLOW; + } +#ifdef DEBUG + char szParmThreadId[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", + s_uCtrlSessionThread)) + { + rc = VERR_BUFFER_OVERFLOW; + } +#endif /* DEBUG */ + if (RT_SUCCESS(rc)) + { + int iOptIdx = 0; /* Current index in argument vector. */ + + char const *papszArgs[16]; + papszArgs[iOptIdx++] = pszExeName; + papszArgs[iOptIdx++] = "guestsession"; + papszArgs[iOptIdx++] = szParmSessionID; + papszArgs[iOptIdx++] = szParmSessionProto; +#ifdef DEBUG + papszArgs[iOptIdx++] = szParmThreadId; +#endif /* DEBUG */ + if (!fAnonymous) + papszArgs[iOptIdx++] = szParmUserName; + + /* Add same verbose flags as parent process. */ + int rc2 = VINF_SUCCESS; + char szParmVerbose[32] = { 0 }; + for (int i = 0; i < g_cVerbosity && RT_SUCCESS(rc2); i++) + { + if (i == 0) + rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "-"); + if (RT_FAILURE(rc2)) + break; + rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "v"); + } + if (RT_SUCCESS(rc2)) + papszArgs[iOptIdx++] = szParmVerbose; + + /* Add log file handling. Each session will have an own + * log file, naming based on the parent log file. */ + char szParmLogFile[RTPATH_MAX]; + if ( RT_SUCCESS(rc2) + && strlen(g_szLogFile)) + { + char *pszLogFile = RTStrDup(g_szLogFile); + if (pszLogFile) + { + char *pszLogExt = NULL; + if (RTPathHasExt(pszLogFile)) + pszLogExt = RTStrDup(RTPathExt(pszLogFile)); + RTPathStripExt(pszLogFile); + char *pszLogSuffix; +#ifndef DEBUG + if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%s", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->szUser) < 0) + { + rc2 = VERR_NO_MEMORY; + } +#else + if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%RU32-%s", + pSessionStartupInfo->uSessionID, + s_uCtrlSessionThread, + pSessionStartupInfo->szUser) < 0) + { + rc2 = VERR_NO_MEMORY; + } +#endif /* DEBUG */ + else + { + rc2 = RTStrAAppend(&pszLogFile, pszLogSuffix); + if (RT_SUCCESS(rc2) && pszLogExt) + rc2 = RTStrAAppend(&pszLogFile, pszLogExt); + if (RT_SUCCESS(rc2)) + { + if (!RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), + "--logfile=%s", pszLogFile)) + { + rc2 = VERR_BUFFER_OVERFLOW; + } + } + RTStrFree(pszLogSuffix); + } + if (RT_FAILURE(rc2)) + VBoxServiceError("Error building session logfile string for session %RU32 (user %s), rc=%Rrc\n", + pSessionStartupInfo->uSessionID, pSessionStartupInfo->szUser, rc2); + if (pszLogExt) + RTStrFree(pszLogExt); + RTStrFree(pszLogFile); + } + if (RT_SUCCESS(rc2)) + papszArgs[iOptIdx++] = szParmLogFile; + + rc = rc2; + } + else if (RT_FAILURE(rc2)) + rc = rc2; +#ifdef DEBUG + VBoxServiceVerbose(4, "Argv building rc=%Rrc, session flags=%x\n", + rc, g_Session.uFlags); + char szParmDumpStdOut[32]; + if ( RT_SUCCESS(rc) + && g_Session.uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT) + { + if (!RTStrPrintf(szParmDumpStdOut, sizeof(szParmDumpStdOut), "--dump-stdout")) + rc = VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + papszArgs[iOptIdx++] = szParmDumpStdOut; + } + char szParmDumpStdErr[32]; + if ( RT_SUCCESS(rc) + && g_Session.uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR) + { + if (!RTStrPrintf(szParmDumpStdErr, sizeof(szParmDumpStdErr), "--dump-stderr")) + rc = VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + papszArgs[iOptIdx++] = szParmDumpStdErr; + } +#endif + papszArgs[iOptIdx++] = NULL; + + if (g_cVerbosity > 3) + { + VBoxServiceVerbose(4, "Forking parameters:\n"); + + iOptIdx = 0; + while (papszArgs[iOptIdx]) + VBoxServiceVerbose(4, "\t%s\n", papszArgs[iOptIdx++]); + } + + uint32_t uProcFlags = RTPROC_FLAGS_SERVICE + | RTPROC_FLAGS_HIDDEN; /** @todo More flags from startup info? */ + +#if 0 /* Pipe handling not needed (yet). */ + /* Setup pipes. */ + rc = GstcntlProcessSetupPipe("|", 0 /*STDIN_FILENO*/, + &pSession->StdIn.hChild, &pSession->StdIn.phChild, &pSession->hStdInW); + if (RT_SUCCESS(rc)) + { + rc = GstcntlProcessSetupPipe("|", 1 /*STDOUT_FILENO*/, + &pSession->StdOut.hChild, &pSession->StdOut.phChild, &pSession->hStdOutR); + if (RT_SUCCESS(rc)) + { + rc = GstcntlProcessSetupPipe("|", 2 /*STDERR_FILENO*/, + &pSession->StdErr.hChild, &pSession->StdErr.phChild, &pSession->hStdErrR); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetCreate(&pSession->hPollSet); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdInW, RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDIN); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDOUT); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDERR); + } + + if (RT_SUCCESS(rc)) + { + /* Fork the thing. */ + /** @todo Do we need a custom environment block? */ + rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags, + pSession->StdIn.phChild, pSession->StdOut.phChild, pSession->StdErr.phChild, + !fAnonymous ? pSession->StartupInfo.szUser : NULL, + !fAnonymous ? pSession->StartupInfo.szPassword : NULL, + &pSession->hProcess); + } + + if (RT_SUCCESS(rc)) + { + /* + * Close the child ends of any pipes and redirected files. + */ + int rc2 = RTHandleClose(pSession->StdIn.phChild); AssertRC(rc2); + pSession->StdIn.phChild = NULL; + rc2 = RTHandleClose(pSession->StdOut.phChild); AssertRC(rc2); + pSession->StdOut.phChild = NULL; + rc2 = RTHandleClose(pSession->StdErr.phChild); AssertRC(rc2); + pSession->StdErr.phChild = NULL; + } + } + } +#else + RTHANDLE hStdIn; + if (RT_SUCCESS(rc)) + rc = RTFileOpenBitBucket(&hStdIn.u.hFile, RTFILE_O_READ); + if (RT_SUCCESS(rc)) + { + hStdIn.enmType = RTHANDLETYPE_FILE; + + RTHANDLE hStdOutAndErr; + rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE); + if (RT_SUCCESS(rc)) + { + hStdOutAndErr.enmType = RTHANDLETYPE_FILE; + + /** @todo Set custom/cloned guest session environment block. */ + rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags, + &hStdIn, &hStdOutAndErr, &hStdOutAndErr, + !fAnonymous ? pSessionThread->StartupInfo.szUser : NULL, + !fAnonymous ? pSessionThread->StartupInfo.szPassword : NULL, + &pSessionThread->hProcess); + + RTFileClose(hStdOutAndErr.u.hFile); + } + + RTFileClose(hStdIn.u.hFile); + } +#endif + } + } + else + rc = VERR_FILE_NOT_FOUND; + + if (RT_SUCCESS(rc)) + { + /* Start session thread. */ + rc = RTThreadCreateF(&pSessionThread->Thread, gstcntlSessionThread, + pSessionThread /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sess%u", s_uCtrlSessionThread); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Creating session thread failed, rc=%Rrc\n", rc); + } + else + { + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pSessionThread->Thread, 60 * 1000 /* 60s timeout */); + if ( ASMAtomicReadBool(&pSessionThread->fShutdown) + || RT_FAILURE(rc)) + { + VBoxServiceError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n", + pSessionThread->StartupInfo.uSessionID, rc); + if (RT_SUCCESS(rc)) + rc = VERR_CANT_CREATE; /** @todo Find a better rc. */ + } + else + { + VBoxServiceVerbose(2, "Thread for session ID=%RU32 started\n", + pSessionThread->StartupInfo.uSessionID); + + ASMAtomicXchgBool(&pSessionThread->fStarted, true); + + /* Add session to list. */ + /* rc = */ RTListAppend(pList, &pSessionThread->Node); + if (ppSessionThread) /* Return session if wanted. */ + *ppSessionThread = pSessionThread; + } + } + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pSessionThread); + } + } + else + rc = VERR_NO_MEMORY; + + VBoxServiceVerbose(3, "Forking session thread returned returned rc=%Rrc\n", rc); + return rc; +} + + +/** + * Waits for a formerly opened guest session process to close. + * + * @return IPRT status code. + * @param pThread Guest session thread to wait for. + * @param uTimeoutMS Waiting timeout (in ms). + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, + uint32_t uTimeoutMS, uint32_t uFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + /** @todo Validate closing flags. */ + + if (pThread->Thread == NIL_RTTHREAD) + { + AssertMsgFailed(("Guest session thread of session %p does not exist when it should\n", + pThread)); + return VERR_NOT_FOUND; + } + + int rc = VINF_SUCCESS; + + /* + * The fork should have received the same closing request, + * so just wait for the process to close. + */ + if (ASMAtomicReadBool(&pThread->fStarted)) + { + /* Ask the thread to shutdown. */ + ASMAtomicXchgBool(&pThread->fShutdown, true); + + VBoxServiceVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n", + pThread->StartupInfo.uSessionID, uTimeoutMS); + + int rcThread; + rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", + pThread->StartupInfo.uSessionID, rc); + } + else + VBoxServiceVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", + pThread->StartupInfo.uSessionID, rcThread); + } + + return rc; +} + +/** + * Waits for the specified session thread to end and remove + * it from the session thread list. + * + * @return IPRT status code. + * @param pThread Session thread to destroy. + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + int rc = GstCntlSessionThreadWait(pThread, + 5 * 60 * 1000 /* 5 minutes timeout */, uFlags); + + /* Remove session from list and destroy object. */ + RTListNodeRemove(&pThread->Node); + + RTMemFree(pThread); + pThread = NULL; + + return rc; +} + +/** + * Close all formerly opened guest session threads. + * Note: Caller is responsible for locking! + * + * @return IPRT status code. + * @param pList Which list to close the session threads for. + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + /*int rc = VbglR3GuestCtrlClose + if (RT_FAILURE(rc)) + VBoxServiceError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/ + + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread + = RTListGetFirst(pList, VBOXSERVICECTRLSESSIONTHREAD, Node); + while (pSessionThread) + { + PVBOXSERVICECTRLSESSIONTHREAD pSessionThreadNext = + RTListGetNext(pList, pSessionThread, VBOXSERVICECTRLSESSIONTHREAD, Node); + bool fLast = RTListNodeIsLast(pList, &pSessionThread->Node); + + int rc2 = GstCntlSessionThreadDestroy(pSessionThread, uFlags); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("Closing session thread failed with rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + if (fLast) + break; + + pSessionThread = pSessionThreadNext; + } + + return rc; +} + +RTEXITCODE VBoxServiceControlSessionForkInit(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { +#ifdef DEBUG + { "--dump-stdout", VBOXSERVICESESSIONOPT_DUMP_STDOUT, RTGETOPT_REQ_NOTHING }, + { "--dump-stderr", VBOXSERVICESESSIONOPT_DUMP_STDERR, RTGETOPT_REQ_NOTHING }, +#endif + { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING }, + { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING }, + { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 }, + { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 }, +#ifdef DEBUG + { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 }, +#endif /* DEBUG */ + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, + s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + uint32_t uSessionFlags = VBOXSERVICECTRLSESSION_FLAG_FORK; + + /* Protocol and session ID must be specified explicitly. */ + g_Session.StartupInfo.uProtocol = UINT32_MAX; + g_Session.StartupInfo.uSessionID = UINT32_MAX; + + int rc = VINF_SUCCESS; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case VBOXSERVICESESSIONOPT_LOG_FILE: + if (!RTStrPrintf(g_szLogFile, sizeof(g_szLogFile), "%s", ValueUnion.psz)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unable to set logfile name to '%s'", + ValueUnion.psz); + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_DUMP_STDOUT: + uSessionFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + break; + + case VBOXSERVICESESSIONOPT_DUMP_STDERR: + uSessionFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + break; +#endif + case VBOXSERVICESESSIONOPT_USERNAME: + /** @todo Information not needed right now, skip. */ + break; + + case VBOXSERVICESESSIONOPT_SESSION_ID: + g_Session.StartupInfo.uSessionID = ValueUnion.u32; + break; + + case VBOXSERVICESESSIONOPT_SESSION_PROTO: + g_Session.StartupInfo.uProtocol = ValueUnion.u32; + break; + +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_THREAD_ID: + /* Not handled. */ + break; +#endif + /** @todo Implement help? */ + + case 'v': + g_cVerbosity++; + break; + + case VINF_GETOPT_NOT_OPTION: + /* Ignore; might be "guestsession" main command. */ + break; + + default: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown command '%s'", ValueUnion.psz); + break; /* Never reached. */ + } + } + + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Initialization failed with rc=%Rrc", rc); + + if (g_Session.StartupInfo.uProtocol == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified"); + + if (g_Session.StartupInfo.uSessionID == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified"); + + /* Init the session object. */ + rc = GstCntlSessionInit(&g_Session, uSessionFlags); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize session object, rc=%Rrc\n", rc); + + rc = VBoxServiceLogCreate(strlen(g_szLogFile) ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)", + strlen(g_szLogFile) ? g_szLogFile : "<None>", rc); + + RTEXITCODE rcExit = gstcntlSessionForkWorker(&g_Session); + + VBoxServiceLogDestroy(); + return rcExit; +} + |