/* $Id: VBoxManageGuestCtrl.cpp 48014 2013-08-23 09:01:29Z vboxsync $ */ /** @file * VBoxManage - Implementation of guestcontrol command. */ /* * Copyright (C) 2010-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 "VBoxManage.h" #ifndef VBOX_ONLY_DOCS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For RTProcSelf(). */ #include #include #include #ifdef USE_XPCOM_QUEUE # include # include #endif #include #ifdef RT_OS_DARWIN # include #endif using namespace com; /** @todo Move this into a helper module. */ static const char *ctrlFileStatusToText(FileStatus_T enmStatus); static const char *ctrlProcessStatusToText(ProcessStatus_T enmStatus); static const char *ctrlSessionStatusToText(GuestSessionStatus_T enmStatus); class GuestFileEventListener; typedef ListenerImpl GuestFileEventListenerImpl; VBOX_LISTENER_DECLARE(GuestFileEventListenerImpl) class GuestProcessEventListener; typedef ListenerImpl GuestProcessEventListenerImpl; VBOX_LISTENER_DECLARE(GuestProcessEventListenerImpl) class GuestSessionEventListener; typedef ListenerImpl GuestSessionEventListenerImpl; VBOX_LISTENER_DECLARE(GuestSessionEventListenerImpl) /** Simple statistics class for binding locally * held data to a specific guest object. */ class GuestEventStats { public: GuestEventStats(void) : uLastUpdatedMS(RTTimeMilliTS()) { } /** @todo Make this more a class than a structure. */ public: uint64_t uLastUpdatedMS; }; class GuestFileStats : public GuestEventStats { public: GuestFileStats(void) { } GuestFileStats(ComObjPtr pListenerImpl) : mListener(pListenerImpl) { } public: /** @todo */ ComObjPtr mListener; }; class GuestProcStats : public GuestEventStats { public: GuestProcStats(void) { } GuestProcStats(ComObjPtr pListenerImpl) : mListener(pListenerImpl) { } public: /** @todo */ ComObjPtr mListener; }; class GuestSessionStats : public GuestEventStats { public: GuestSessionStats(void) { } GuestSessionStats(ComObjPtr pListenerImpl) : mListener(pListenerImpl) { } public: /** @todo */ ComObjPtr mListener; }; /** Map containing all watched guest files. */ typedef std::map< ComPtr, GuestFileStats > GuestEventFiles; /** Map containing all watched guest processes. */ typedef std::map< ComPtr, GuestProcStats > GuestEventProcs; /** Map containing all watched guest sessions. */ typedef std::map< ComPtr, GuestSessionStats > GuestEventSessions; class GuestListenerBase { public: GuestListenerBase(void) : mfVerbose(false) { } virtual ~GuestListenerBase(void) { } HRESULT init(bool fVerbose = false) { mfVerbose = fVerbose; return S_OK; } protected: /** Verbose flag. */ bool mfVerbose; }; /** * Handler for guest process events. */ class GuestFileEventListener : public GuestListenerBase { public: GuestFileEventListener(void) { } virtual ~GuestFileEventListener(void) { } void uninit(void) { } STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) { switch (aType) { case VBoxEventType_OnGuestFileStateChanged: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pProcess; CHECK_ERROR_BREAK(pEvent, COMGETTER(File)(pProcess.asOutParam())); AssertBreak(!pProcess.isNull()); FileStatus_T fileSts; CHECK_ERROR_BREAK(pEvent, COMGETTER(Status)(&fileSts)); Bstr strPath; CHECK_ERROR_BREAK(pProcess, COMGETTER(FileName)(strPath.asOutParam())); ULONG uID; CHECK_ERROR_BREAK(pProcess, COMGETTER(Id)(&uID)); RTPrintf("File ID=%RU32 \"%s\" changed status to [%s]\n", uID, Utf8Str(strPath).c_str(), ctrlFileStatusToText(fileSts)); } while (0); break; } default: AssertFailed(); } return S_OK; } protected: }; /** * Handler for guest process events. */ class GuestProcessEventListener : public GuestListenerBase { public: GuestProcessEventListener(void) { } virtual ~GuestProcessEventListener(void) { } void uninit(void) { } STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) { switch (aType) { case VBoxEventType_OnGuestProcessStateChanged: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pProcess; CHECK_ERROR_BREAK(pEvent, COMGETTER(Process)(pProcess.asOutParam())); AssertBreak(!pProcess.isNull()); ProcessStatus_T procSts; CHECK_ERROR_BREAK(pEvent, COMGETTER(Status)(&procSts)); Bstr strPath; CHECK_ERROR_BREAK(pProcess, COMGETTER(ExecutablePath)(strPath.asOutParam())); ULONG uPID; CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID)); RTPrintf("Process PID=%RU32 \"%s\" changed status to [%s]\n", uPID, Utf8Str(strPath).c_str(), ctrlProcessStatusToText(procSts)); } while (0); break; } default: AssertFailed(); } return S_OK; } protected: }; /** * Handler for guest session events. */ class GuestSessionEventListener : public GuestListenerBase { public: GuestSessionEventListener(void) { } virtual ~GuestSessionEventListener(void) { } void uninit(void) { GuestEventProcs::iterator itProc = mProcs.begin(); while (itProc != mProcs.end()) { if (!itProc->first.isNull()) { HRESULT rc; do { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR_BREAK(itProc->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR_BREAK(pES, UnregisterListener(itProc->second.mListener)); } while (0); itProc->first->Release(); } itProc++; } mProcs.clear(); GuestEventFiles::iterator itFile = mFiles.begin(); while (itFile != mFiles.end()) { if (!itFile->first.isNull()) { HRESULT rc; do { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR_BREAK(itFile->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR_BREAK(pES, UnregisterListener(itFile->second.mListener)); } while (0); itFile->first->Release(); } itFile++; } mFiles.clear(); } STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) { switch (aType) { case VBoxEventType_OnGuestFileRegistered: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pFile; CHECK_ERROR_BREAK(pEvent, COMGETTER(File)(pFile.asOutParam())); AssertBreak(!pFile.isNull()); BOOL fRegistered; CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); Bstr strPath; CHECK_ERROR_BREAK(pFile, COMGETTER(FileName)(strPath.asOutParam())); RTPrintf("File \"%s\" %s\n", Utf8Str(strPath).c_str(), fRegistered ? "registered" : "unregistered"); if (fRegistered) { if (mfVerbose) RTPrintf("Registering ...\n"); /* Register for IGuestFile events. */ ComObjPtr pListener; pListener.createObject(); CHECK_ERROR_BREAK(pListener, init(new GuestFileEventListener())); ComPtr es; CHECK_ERROR_BREAK(pFile, COMGETTER(EventSource)(es.asOutParam())); com::SafeArray eventTypes; eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */)); GuestFileStats fileStats(pListener); mFiles[pFile] = fileStats; } else { GuestEventFiles::iterator itFile = mFiles.find(pFile); if (itFile != mFiles.end()) { if (mfVerbose) RTPrintf("Unregistering file ...\n"); if (!itFile->first.isNull()) { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR(itFile->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR(pES, UnregisterListener(itFile->second.mListener)); itFile->first->Release(); } mFiles.erase(itFile); } } } while (0); break; } case VBoxEventType_OnGuestProcessRegistered: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pProcess; CHECK_ERROR_BREAK(pEvent, COMGETTER(Process)(pProcess.asOutParam())); AssertBreak(!pProcess.isNull()); BOOL fRegistered; CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); Bstr strPath; CHECK_ERROR_BREAK(pProcess, COMGETTER(ExecutablePath)(strPath.asOutParam())); RTPrintf("Process \"%s\" %s\n", Utf8Str(strPath).c_str(), fRegistered ? "registered" : "unregistered"); if (fRegistered) { if (mfVerbose) RTPrintf("Registering ...\n"); /* Register for IGuestProcess events. */ ComObjPtr pListener; pListener.createObject(); CHECK_ERROR_BREAK(pListener, init(new GuestProcessEventListener())); ComPtr es; CHECK_ERROR_BREAK(pProcess, COMGETTER(EventSource)(es.asOutParam())); com::SafeArray eventTypes; eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */)); GuestProcStats procStats(pListener); mProcs[pProcess] = procStats; } else { GuestEventProcs::iterator itProc = mProcs.find(pProcess); if (itProc != mProcs.end()) { if (mfVerbose) RTPrintf("Unregistering process ...\n"); if (!itProc->first.isNull()) { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR(itProc->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR(pES, UnregisterListener(itProc->second.mListener)); itProc->first->Release(); } mProcs.erase(itProc); } } } while (0); break; } case VBoxEventType_OnGuestSessionStateChanged: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pSession; CHECK_ERROR_BREAK(pEvent, COMGETTER(Session)(pSession.asOutParam())); AssertBreak(!pSession.isNull()); GuestSessionStatus_T sessSts; CHECK_ERROR_BREAK(pSession, COMGETTER(Status)(&sessSts)); ULONG uID; CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); Bstr strName; CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); RTPrintf("Session ID=%RU32 \"%s\" changed status to [%s]\n", uID, Utf8Str(strName).c_str(), ctrlSessionStatusToText(sessSts)); } while (0); break; } default: AssertFailed(); } return S_OK; } protected: GuestEventFiles mFiles; GuestEventProcs mProcs; }; /** * Handler for guest events. */ class GuestEventListener : public GuestListenerBase { public: GuestEventListener(void) { } virtual ~GuestEventListener(void) { } void uninit(void) { GuestEventSessions::iterator itSession = mSessions.begin(); while (itSession != mSessions.end()) { if (!itSession->first.isNull()) { HRESULT rc; do { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR_BREAK(itSession->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR_BREAK(pES, UnregisterListener(itSession->second.mListener)); } while (0); itSession->first->Release(); } itSession++; } mSessions.clear(); } STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) { switch (aType) { case VBoxEventType_OnGuestSessionRegistered: { HRESULT rc; do { ComPtr pEvent = aEvent; Assert(!pEvent.isNull()); ComPtr pSession; CHECK_ERROR_BREAK(pEvent, COMGETTER(Session)(pSession.asOutParam())); AssertBreak(!pSession.isNull()); BOOL fRegistered; CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); Bstr strName; CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); ULONG uID; CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); RTPrintf("Session ID=%RU32 \"%s\" %s\n", uID, Utf8Str(strName).c_str(), fRegistered ? "registered" : "unregistered"); if (fRegistered) { if (mfVerbose) RTPrintf("Registering ...\n"); /* Register for IGuestSession events. */ ComObjPtr pListener; pListener.createObject(); CHECK_ERROR_BREAK(pListener, init(new GuestSessionEventListener())); ComPtr es; CHECK_ERROR_BREAK(pSession, COMGETTER(EventSource)(es.asOutParam())); com::SafeArray eventTypes; eventTypes.push_back(VBoxEventType_OnGuestFileRegistered); eventTypes.push_back(VBoxEventType_OnGuestProcessRegistered); CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */)); GuestSessionStats sessionStats(pListener); mSessions[pSession] = sessionStats; } else { GuestEventSessions::iterator itSession = mSessions.find(pSession); if (itSession != mSessions.end()) { if (mfVerbose) RTPrintf("Unregistering ...\n"); if (!itSession->first.isNull()) { /* Listener unregistration. */ ComPtr pES; CHECK_ERROR_BREAK(itSession->first, COMGETTER(EventSource)(pES.asOutParam())); if (!pES.isNull()) CHECK_ERROR_BREAK(pES, UnregisterListener(itSession->second.mListener)); itSession->first->Release(); } mSessions.erase(itSession); } } } while (0); break; } default: AssertFailed(); } return S_OK; } protected: GuestEventSessions mSessions; }; typedef ListenerImpl GuestEventListenerImpl; VBOX_LISTENER_DECLARE(GuestEventListenerImpl) /** Set by the signal handler. */ static volatile bool g_fGuestCtrlCanceled = false; /** Our global session object which is also used in the * signal handler to abort operations properly. */ static ComPtr g_pGuestSession; typedef struct COPYCONTEXT { COPYCONTEXT() : fVerbose(false), fDryRun(false), fHostToGuest(false) { } ComPtr pGuestSession; bool fVerbose; bool fDryRun; bool fHostToGuest; } COPYCONTEXT, *PCOPYCONTEXT; /** * An entry for a source element, including an optional DOS-like wildcard (*,?). */ class SOURCEFILEENTRY { public: SOURCEFILEENTRY(const char *pszSource, const char *pszFilter) : mSource(pszSource), mFilter(pszFilter) {} SOURCEFILEENTRY(const char *pszSource) : mSource(pszSource) { Parse(pszSource); } const char* GetSource() const { return mSource.c_str(); } const char* GetFilter() const { return mFilter.c_str(); } private: int Parse(const char *pszPath) { AssertPtrReturn(pszPath, VERR_INVALID_POINTER); if ( !RTFileExists(pszPath) && !RTDirExists(pszPath)) { /* No file and no directory -- maybe a filter? */ char *pszFilename = RTPathFilename(pszPath); if ( pszFilename && strpbrk(pszFilename, "*?")) { /* Yep, get the actual filter part. */ mFilter = RTPathFilename(pszPath); /* Remove the filter from actual sourcec directory name. */ RTPathStripFilename(mSource.mutableRaw()); mSource.jolt(); } } return VINF_SUCCESS; /* @todo */ } private: Utf8Str mSource; Utf8Str mFilter; }; typedef std::vector SOURCEVEC, *PSOURCEVEC; /** * An entry for an element which needs to be copied/created to/on the guest. */ typedef struct DESTFILEENTRY { DESTFILEENTRY(Utf8Str strFileName) : mFileName(strFileName) {} Utf8Str mFileName; } DESTFILEENTRY, *PDESTFILEENTRY; /* * Map for holding destination entires, whereas the key is the destination * directory and the mapped value is a vector holding all elements for this directoy. */ typedef std::map< Utf8Str, std::vector > DESTDIRMAP, *PDESTDIRMAP; typedef std::map< Utf8Str, std::vector >::iterator DESTDIRMAPITER, *PDESTDIRMAPITER; /** * Special exit codes for returning errors/information of a * started guest process to the command line VBoxManage was started from. * Useful for e.g. scripting. * * @note These are frozen as of 4.1.0. */ enum EXITCODEEXEC { EXITCODEEXEC_SUCCESS = RTEXITCODE_SUCCESS, /* Process exited normally but with an exit code <> 0. */ EXITCODEEXEC_CODE = 16, EXITCODEEXEC_FAILED = 17, EXITCODEEXEC_TERM_SIGNAL = 18, EXITCODEEXEC_TERM_ABEND = 19, EXITCODEEXEC_TIMEOUT = 20, EXITCODEEXEC_DOWN = 21, EXITCODEEXEC_CANCELED = 22 }; /** * RTGetOpt-IDs for the guest execution control command line. */ enum GETOPTDEF_EXEC { GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000, GETOPTDEF_EXEC_NO_PROFILE, GETOPTDEF_EXEC_OUTPUTFORMAT, GETOPTDEF_EXEC_DOS2UNIX, GETOPTDEF_EXEC_UNIX2DOS, GETOPTDEF_EXEC_PASSWORD, GETOPTDEF_EXEC_WAITFOREXIT, GETOPTDEF_EXEC_WAITFORSTDOUT, GETOPTDEF_EXEC_WAITFORSTDERR }; enum GETOPTDEF_COPY { GETOPTDEF_COPY_DRYRUN = 1000, GETOPTDEF_COPY_FOLLOW, GETOPTDEF_COPY_PASSWORD, GETOPTDEF_COPY_TARGETDIR }; enum GETOPTDEF_MKDIR { GETOPTDEF_MKDIR_PASSWORD = 1000 }; enum GETOPTDEF_SESSIONCLOSE { GETOPTDEF_SESSIONCLOSE_ALL = 1000 }; enum GETOPTDEF_STAT { GETOPTDEF_STAT_PASSWORD = 1000 }; enum OUTPUTTYPE { OUTPUTTYPE_UNDEFINED = 0, OUTPUTTYPE_DOS2UNIX = 10, OUTPUTTYPE_UNIX2DOS = 20 }; static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest, const char *pszDir, bool *fExists); #endif /* VBOX_ONLY_DOCS */ void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2) { RTStrmPrintf(pStrm, "%s guestcontrol %s \n" " exec[ute]\n" " --image --username \n" " [--passwordfile | --password ]\n" " [--domain ] [--verbose] [--timeout ]\n" " [--environment \"= [=]\"]\n" " [--wait-exit] [--wait-stdout] [--wait-stderr]\n" " [--dos2unix] [--unix2dos]\n" " [-- [] ... []]\n" /** @todo Add a "--" parameter (has to be last parameter) to directly execute * stuff, e.g. "VBoxManage guestcontrol execute --username <> ... -- /bin/rm -Rf /foo". */ "\n" " copyfrom\n" " --username \n" " [--passwordfile | --password ]\n" " [--domain ] [--verbose]\n" " [--dryrun] [--follow] [--recursive]\n" "\n" " copyto|cp\n" " --username \n" " [--passwordfile | --password ]\n" " [--domain ] [--verbose]\n" " [--dryrun] [--follow] [--recursive]\n" "\n" " createdir[ectory]|mkdir|md\n" " ... --username \n" " [--passwordfile | --password ]\n" " [--domain ] [--verbose]\n" " [--parents] [--mode ]\n" "\n" " createtemp[orary]|mktemp\n" "