VBoxManageGuestCtrl.cpp revision 4c8907a420ed66a42f729eb08cddf9c1e57f25ea
/* $Id$ */
/** @file
* VBoxManage - Implementation of guestcontrol command.
*/
/*
* Copyright (C) 2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "VBoxManage.h"
#ifndef VBOX_ONLY_DOCS
#ifdef USE_XPCOM_QUEUE
# include <errno.h>
#endif
#include <signal.h>
#ifdef RT_OS_DARWIN
# include <CoreFoundation/CFRunLoop.h>
#endif
using namespace com;
/**
* IVirtualBoxCallback implementation for handling the GuestControlCallback in
* relation to the "guestcontrol * wait" command.
*/
/** @todo */
/** Set by the signal handler. */
static volatile bool g_fGuestCtrlCanceled = false;
/*
* Structure holding a directory entry.
*/
typedef struct DIRECTORYENTRY
{
char *pszSourcePath;
char *pszDestPath;
#endif /* VBOX_ONLY_DOCS */
{
"VBoxManage guestcontrol exec[ute] <vmname>|<uuid>\n"
" <path to program>\n"
" --username <name> --password <password>\n"
" [--arguments \"<arguments>\"]\n"
" [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
" [--flags <flags>] [--timeout <msec>]\n"
" [--verbose] [--wait-for exit,stdout,stderr||]\n"
/** @todo Add a "--" parameter (has to be last parameter) to directly execute
"\n"
" copyto|cp <vmname>|<uuid>\n"
" <source on host> <destination on guest>\n"
" --username <name> --password <password>\n"
" [--dryrun] [--follow] [--recursive] [--verbose]\n"
"\n"
" createdir[ectory]|mkdir|md <vmname>|<uuid>\n"
" <directory to create on guest>\n"
" --username <name> --password <password>\n"
" [--parents] [--mode <mode>] [--verbose]\n"
"\n"
" updateadditions <vmname>|<uuid>\n"
" [--source <guest additions .ISO>] [--verbose]\n"
"\n");
}
#ifndef VBOX_ONLY_DOCS
/**
* Signal handler that sets g_fGuestCtrlCanceled.
*
* This can be executed on any thread in the process, on Windows it may even be
* a thread dedicated to delivering this signal. Do not doing anything
* unnecessary here.
*/
static void guestCtrlSignalHandler(int iSignal)
{
ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
}
/**
* Installs a custom signal handler to get notified
* whenever the user wants to intercept the program.
*/
static void ctrlSignalHandlerInstall()
{
#ifdef SIGBREAK
#endif
}
/**
* Uninstalls a previously installed signal handler.
*/
static void ctrlSignalHandlerUninstall()
{
#ifdef SIGBREAK
#endif
}
/**
* Translates a process status to a human readable
* string.
*/
{
switch (uStatus)
{
case guestControl::PROC_STS_STARTED:
return "started";
case guestControl::PROC_STS_TEN:
return "successfully terminated";
case guestControl::PROC_STS_TES:
return "terminated by signal";
case guestControl::PROC_STS_TEA:
return "abnormally aborted";
case guestControl::PROC_STS_TOK:
return "timed out";
case guestControl::PROC_STS_TOA:
return "timed out, hanging";
case guestControl::PROC_STS_DWN:
return "killed";
case guestControl::PROC_STS_ERROR:
return "error";
default:
return "unknown";
}
}
{
if ( errorInfo.isFullAvailable()
|| errorInfo.isBasicAvailable())
{
/* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
* because it contains more accurate info about what went wrong. */
else
{
RTMsgError("Error details:");
}
return VERR_GENERAL_FAILURE; /** @todo */
}
AssertMsgFailedReturn(("Object has indicated no error!?\n"),
}
{
return ctrlPrintError(ErrInfo);
}
{
int rc;
&& fCanceled)
{
rc = VERR_CANCELLED;
}
else
{
}
return rc;
}
/**
* Un-initializes the VM after guest control usage.
*/
{
}
/**
* Initializes the VM, that is checks whether it's up and
* running, if it can be locked (shared only) and returns a
* valid IGuest pointer on success.
*
* @return IPRT status code.
* @param pArg Our command line argument structure.
* @param pszNameOrId The VM's name or UUID to use.
* @param pGuest Pointer where to store the IGuest interface.
*/
{
/* Lookup VM. */
/* Assume it's an UUID. */
machine.asOutParam()));
return VERR_NOT_FOUND;
/* Machine is running? */
if (machineState != MachineState_Running)
{
return VERR_VM_INVALID_VM_STATE;
}
do
{
/* Open a session for the VM. */
/* Get the associated console. */
/* ... and session machine. */
/* Get IGuest interface. */
} while (0);
}
static int handleCtrlExecProgram(HandlerArg *a)
{
/*
* Check the syntax. We can deduce the correct syntax from the number of
* arguments.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
int ch;
/* Note: this uses IN_BSTR as it must be BSTR on COM and CBSTR on XPCOM */
uint32_t u32TimeoutMS = 0;
bool fWaitForExit = false;
bool fWaitForStdOut = false;
bool fWaitForStdErr = false;
bool fVerbose = false;
bool fTimeout = false;
int vrc = VINF_SUCCESS;
bool fUsageOK = true;
&& RT_SUCCESS(vrc))
{
/* For options that require an argument, ValueUnion has received the value. */
switch (ch)
{
case 'a': /* Arguments */
{
char **papszArg;
int cArgs;
if (RT_SUCCESS(vrc))
{
for (int j = 0; j < cArgs; j++)
}
break;
}
case 'e': /* Environment */
{
char **papszArg;
int cArgs;
if (RT_SUCCESS(vrc))
{
for (int j = 0; j < cArgs; j++)
}
break;
}
case 'f': /* Flags */
/** @todo Needs a bit better processing as soon as we have more flags. */
/** @todo Add a hidden flag. */
else
fUsageOK = false;
break;
case 'p': /* Password */
break;
case 't': /* Timeout */
fTimeout = true;
break;
case 'u': /* User name */
break;
case 'v': /* Verbose */
fVerbose = true;
break;
case 'w': /* Wait for ... */
{
fWaitForExit = true;
{
fWaitForExit = true;
fWaitForStdOut = true;
}
{
fWaitForExit = true;
fWaitForStdErr = true;
}
else
fUsageOK = false;
break;
}
case VINF_GETOPT_NOT_OPTION:
{
/* The actual command we want to execute on the guest. */
break;
}
default:
}
}
if (!fUsageOK)
return errorSyntax(USAGE_GUESTCONTROL,
"No command to execute specified!");
if (Utf8UserName.isEmpty())
return errorSyntax(USAGE_GUESTCONTROL,
"No user name specified!");
if (RT_SUCCESS(vrc))
{
if (fVerbose)
{
if (u32TimeoutMS == 0)
RTPrintf("Waiting for guest to start process ...\n");
else
}
/* Get current time stamp to later calculate rest of timeout left. */
/* Execute the process. */
else
{
if (fVerbose)
if (fWaitForExit)
{
if (fTimeout)
{
/* Calculate timeout value left after process has been started. */
/* Is timeout still bigger than current difference? */
if (u32TimeoutMS > u64Elapsed)
{
if (fVerbose)
}
else
{
if (fVerbose)
RTPrintf("No time left to wait for process!\n");
}
}
else if (fVerbose)
RTPrintf("Waiting for process to exit ...\n");
/* Setup signal handling if cancelable. */
bool fCanceledAlready = false;
fCancelable = FALSE;
if (fCancelable)
/* Wait for process to exit ... */
{
ULONG cbOutputData = 0;
/*
* Some data left to output?
*/
if ( fWaitForStdOut
|| fWaitForStdErr)
{
{
cbOutputData = 0;
fCompleted = true; /* rc contains a failure, so we'll go into aborted state down below. */
}
else
{
if (cbOutputData > 0)
{
/* aOutputData has a platform dependent line ending, standardize on
s++, d++)
{
if (*s == '\r')
{
/* skip over CR, adjust destination */
d--;
}
else if (s != d)
*d = *s;
}
}
}
}
if (cbOutputData <= 0) /* No more output data left? */
{
if (fCompleted)
break;
if ( fTimeout
{
break;
}
}
/* Process async cancelation */
if (g_fGuestCtrlCanceled && !fCanceledAlready)
{
else
g_fGuestCtrlCanceled = false;
}
/* Progress canceled by Main API? */
&& fCanceled)
{
break;
}
}
/* Undo signal handling */
if (fCancelable)
if (fCanceled)
{
if (fVerbose)
RTPrintf("Process execution canceled!\n");
}
else if ( fCompleted
{
{
}
else if (fVerbose)
{
RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, uRetStatus, ctrlExecGetStatus(uRetStatus), uRetFlags);
}
}
else
{
if (fVerbose)
RTPrintf("Process execution aborted!\n");
}
}
}
ctrlUninitVM(a);
}
if (RT_FAILURE(vrc))
}
/**
*
* @return IPRT status code.
* @param pszFileSource Full qualified source path of file to copy (optional).
* @param pszFileDest Full qualified destination path (optional).
* @param pList Copy list used for insertion.
*/
{
return VERR_NO_MEMORY;
if (pszFileSource)
{
}
if (pszFileDest)
{
}
return VINF_SUCCESS;
}
/**
* Destroys a directory list.
*
* @param pList Pointer to list to destroy.
*/
{
/* Destroy file list. */
while (pNode)
{
if (pNode->pszSourcePath)
if (pNode->pszDestPath)
if (fLast)
break;
}
}
/**
* Reads a specified directory (recursively) based on the copy flags
* and appends all matching entries to the supplied list.
*
* @return IPRT status code.
* @param pszRootDir Directory to start with. Must end with
* a trailing slash and must be absolute.
* @param pszSubDir Sub directory part relative to the root
* directory; needed for recursion.
* @param pszFilter Search filter (e.g. *.pdf).
* @param pszDest Destination directory.
* @param uFlags Copy flags.
* @param pcObjects Where to store the overall objects to
* copy found.
* @param pList Pointer to the object list to use.
*/
{
/* Sub directory is optional. */
/* Filter directory is optional. */
int rc = VINF_SUCCESS;
char szCurDir[RTPATH_MAX];
/* Construct current path. */
{
}
else
rc = VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
/* Open directory without a filter - RTDirOpenFiltered unfortunately
* cannot handle sub directories so we have to do the filtering ourselves. */
for (;RT_SUCCESS(rc);)
{
if (RT_FAILURE(rc))
{
if (rc == VERR_NO_MORE_FILES)
rc = VINF_SUCCESS;
break;
}
{
case RTDIRENTRYTYPE_DIRECTORY:
/* Skip "." and ".." entrires. */
{
break;
}
if (uFlags & CopyFileFlag_Recursive)
{
if (pszSubDir)
else
if (pszNewSub)
{
}
else
rc = VERR_NO_MEMORY;
}
break;
case RTDIRENTRYTYPE_SYMLINK:
if ( (uFlags & CopyFileFlag_Recursive)
&& (uFlags & CopyFileFlag_FollowLinks))
{
/* Fall through to next case is intentional. */
}
else
break;
case RTDIRENTRYTYPE_FILE:
{
bool fProcess = false;
fProcess = true;
else if (!pszFilter)
fProcess = true;
if (fProcess)
{
char *pszFileSource = NULL;
char *pszFileDest = NULL;
{
{
rc = VERR_NO_MEMORY;
}
}
else
rc = VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
}
if (pszFileSource)
if (pszFileDest)
}
}
break;
default:
break;
}
if (RT_FAILURE(rc))
break;
}
}
if (pDir)
return rc;
}
/**
* Initializes the copy process and builds up an object list
* with all required information to start the actual copy process.
*
* @return IPRT status code.
* @param pszSource Source path on host to use.
* @param pszDest Destination path on guest to use.
* @param uFlags Copy flags.
* @param pcObjects Where to store the count of objects to be copied.
* @param pList Where to store the object list.
*/
{
int rc = VINF_SUCCESS;
if (pszSourceAbs)
{
if ( RTPathFilename(pszSourceAbs)
{
if (pszDestAbs)
{
/* Do we have a trailing slash for the destination?
* Then this is a directory ... */
if ( cch > 1
)
)
{
}
else
{
/* Since the desetination seems not to be a directory,
* we assume that this is the absolute path to the destination
* file -> nothing to do here ... */
}
if (RT_SUCCESS(rc))
{
*pcObjects = 1;
}
}
else
rc = VERR_NO_MEMORY;
}
else /* ... or a directory. */
{
/* Append trailing slash to absolute directory. */
if (RTDirExists(pszSourceAbs))
/* Extract directory filter (e.g. "*.exe"). */
if ( pszSourceAbsRoot
&& pszDestAbs)
{
if (pszFilter)
{
}
else
{
/*
* If we have more than one file to copy, make sure that we have
* a trailing slash so that we can construct a full path name
*/
if ( cch > 1
{
}
}
if (RT_SUCCESS(rc))
{
/*
* Make sure we have a valid destination path. All we can do
* here is to check whether we have a trailing slash -- the rest
* (i.e. path creation, rights etc.) needs to be done inside the guest.
*/
if ( cch > 1
{
}
}
if (RT_SUCCESS(rc))
{
rc = VERR_NOT_FOUND;
}
if (pszDestAbs)
if (pszSourceAbsRoot)
}
else
rc = VERR_NO_MEMORY;
}
}
else
rc = VERR_NO_MEMORY;
return rc;
}
/**
* Copys a file from host to the guest.
*
* @return IPRT status code.
* @param pGuest IGuest interface pointer.
* @param fVerbose Verbose flag.
* @param pszSource Source path of existing host file to copy.
* @param pszDest Destination path on guest to copy the file to.
* @param pszUserName User name on guest to use for the copy operation.
* @param pszPassword Password of user account.
* @param uFlags Copy flags.
*/
static int ctrlCopyFileToGuest(IGuest *pGuest, bool fVerbose, const char *pszSource, const char *pszDest,
const char *pszUserName, const char *pszPassword,
{
int vrc = VINF_SUCCESS;
else
{
}
return vrc;
}
static int handleCtrlCopyTo(HandlerArg *a)
{
/*
* Check the syntax. We can deduce the correct syntax from the number of
* arguments.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
int ch;
bool fVerbose = false;
bool fCopyRecursive = false;
bool fDryRun = false;
int vrc = VINF_SUCCESS;
uint32_t uNoOptionIdx = 0;
bool fUsageOK = true;
&& RT_SUCCESS(vrc))
{
/* For options that require an argument, ValueUnion has received the value. */
switch (ch)
{
case 'd': /* Dry run */
fDryRun = true;
break;
case 'F': /* Follow symlinks */
break;
case 'p': /* Password */
break;
case 'R': /* Recursive processing */
break;
case 'u': /* User name */
break;
case 'v': /* Verbose */
fVerbose = true;
break;
case VINF_GETOPT_NOT_OPTION:
{
/* Get the actual source + destination. */
switch (uNoOptionIdx)
{
case 0:
break;
case 1:
break;
default:
break;
}
uNoOptionIdx++;
if (uNoOptionIdx == UINT32_MAX)
{
RTMsgError("Too many files specified! Aborting.\n");
}
break;
}
default:
}
}
if (!fUsageOK)
if (Utf8Source.isEmpty())
return errorSyntax(USAGE_GUESTCONTROL,
"No source specified!");
return errorSyntax(USAGE_GUESTCONTROL,
"No destination specified!");
if (Utf8UserName.isEmpty())
return errorSyntax(USAGE_GUESTCONTROL,
"No user name specified!");
if (RT_SUCCESS(vrc))
{
if (fVerbose)
{
if (fDryRun)
RTPrintf("Dry run - no files copied!\n");
RTPrintf("Gathering file information ...\n");
}
&cObjects, &listToCopy);
if (RT_FAILURE(vrc))
{
switch (vrc)
{
case VERR_NOT_FOUND:
RTMsgError("No files to copy found!\n");
break;
case VERR_FILE_NOT_FOUND:
break;
default:
break;
}
}
else
{
if (RT_SUCCESS(vrc))
{
if (fVerbose)
{
if (fCopyRecursive)
RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
else
RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
}
{
if (!fDryRun)
{
if (fVerbose)
RTPrintf("Copying \"%s\" to \"%s\" (%u/%u) ...\n",
/* Finally copy the desired file (if no dry run selected). */
if (!fDryRun)
}
if (RT_FAILURE(vrc))
break;
uCurObject++;
}
RTPrintf("Copy operation successful!\n");
}
}
ctrlUninitVM(a);
}
if (RT_FAILURE(vrc))
}
static int handleCtrlCreateDirectory(HandlerArg *a)
{
/*
* Check the syntax. We can deduce the correct syntax from the number of
* arguments.
*/
static const RTGETOPTDEF s_aOptions[] =
{
};
int ch;
bool fVerbose = false;
int vrc = VINF_SUCCESS;
bool fUsageOK = true;
&& RT_SUCCESS(vrc))
{
/* For options that require an argument, ValueUnion has received the value. */
switch (ch)
{
case 'm': /* Mode */
break;
case 'P': /* Create parents */
break;
case 'p': /* Password */
break;
case 'u': /* User name */
break;
case 'v': /* Verbose */
fVerbose = true;
break;
case VINF_GETOPT_NOT_OPTION:
{
&listDirs);
if (RT_SUCCESS(vrc))
{
uNumDirs++;
if (uNumDirs == UINT32_MAX)
{
RTMsgError("Too many directories specified! Aborting.\n");
}
}
break;
}
default:
}
}
if (!fUsageOK)
if (!uNumDirs)
return errorSyntax(USAGE_GUESTCONTROL,
"No directory to create specified!");
if (Utf8UserName.isEmpty())
return errorSyntax(USAGE_GUESTCONTROL,
"No user name specified!");
if (RT_SUCCESS(vrc))
{
{
if (fVerbose)
{
break;
}
}
ctrlUninitVM(a);
}
if (RT_FAILURE(vrc))
}
static int handleCtrlUpdateAdditions(HandlerArg *a)
{
/*
* Check the syntax. We can deduce the correct syntax from the number of
* arguments.
*/
bool fVerbose = false;
/** @todo r=bird: Use RTGetOpt here, no new code using strcmp-if-switching! */
/* Iterate through all possible commands (if available). */
bool usageOK = true;
{
{
if (i + 1 >= a->argc)
usageOK = false;
else
{
++i;
}
}
fVerbose = true;
else
return errorSyntax(USAGE_GUESTCONTROL,
}
if (!usageOK)
if (RT_SUCCESS(vrc))
{
if (fVerbose)
#ifdef DEBUG_andy
if (Utf8Source.isEmpty())
Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
#endif
/* Determine source if not set yet. */
if (Utf8Source.isEmpty())
{
char strTemp[RTPATH_MAX];
/* Check the standard image locations */
else
{
RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
}
}
{
}
if (RT_SUCCESS(vrc))
{
if (fVerbose)
/* Wait for whole update process to complete. */
progress.asOutParam()));
else
{
else if (fVerbose)
RTPrintf("Guest Additions update successful.\n");
}
}
ctrlUninitVM(a);
}
if (RT_FAILURE(vrc))
}
/**
* Access the guest control store.
*
* @returns 0 on success, 1 on failure
* @note see the command line API description for parameters
*/
int handleGuestControl(HandlerArg *a)
{
HandlerArg arg = *a;
if (a->argc <= 0)
/* switch (cmd) */
{
return handleCtrlExecProgram(&arg);
}
{
return handleCtrlCopyTo(&arg);
}
{
return handleCtrlCreateDirectory(&arg);
}
{
return handleCtrlUpdateAdditions(&arg);
}
/* default: */
}
#endif /* !VBOX_ONLY_DOCS */