Index: trunk/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp
===================================================================
--- trunk/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp	(revision 38070)
+++ trunk/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp	(revision 38085)
@@ -69,4 +69,13 @@
 static volatile bool    g_fGuestCtrlCanceled = false;
 
+typedef struct COPYCONTEXT
+{
+    IGuest *pGuest;
+    bool fVerbose;
+    bool fHostToGuest;
+    char *pszUsername;
+    char *pszPassword;
+} COPYCONTEXT, *PCOPYCONTEXT;
+
 /**
  * An entry for a source element, including an optional filter.
@@ -100,5 +109,5 @@
 
 /**
- * An entry for an element which needs to be copied to the guest.
+ * An entry for an element which needs to be copied/created to/on the guest.
  */
 typedef struct DESTFILEENTRY
@@ -187,11 +196,8 @@
                  "                            [--timeout <msec>] [--unix2dos] [--verbose]\n"
                  "                            [--wait-exit] [--wait-stdout] [--wait-stderr]\n"
-                 //"                          [--output-format=<dos>|<unix>]\n"
-                 "                            [--output-type=<binary>|<text>]\n"
                  "                            [-- [<argument1>] ... [<argumentN>]]\n"
                  /** @todo Add a "--" parameter (has to be last parameter) to directly execute
                   *        stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
                  "\n"
-#if 0
                  "                            copyfrom\n"
                  "                            <source on guest> <destination on host>\n"
@@ -199,5 +205,4 @@
                  "                            [--dryrun] [--follow] [--recursive] [--verbose]\n"
                  "\n"
-#endif
                  "                            copyto|cp\n"
                  "                            <source on host> <destination on guest>\n"
@@ -206,7 +211,12 @@
                  "\n"
                  "                            createdir[ectory]|mkdir|md\n"
-                 "                            <directory to create on guest>\n"
+                 "                            <director[y|ies] to create on guest>\n"
                  "                            --username <name> --password <password>\n"
                  "                            [--parents] [--mode <mode>] [--verbose]\n"
+                 "\n"
+                 "                            stat\n"
+                 "                            <file element(s) to check on guest>\n"
+                 "                            --username <name> --password <password>\n"
+                 "                            [--verbose]\n"
                  "\n"
                  "                            updateadditions\n"
@@ -572,4 +582,8 @@
     if (Utf8UserName.isEmpty())
         return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
+
+    /* Any output conversion not supported yet! */
+    if (eOutputType != OUTPUTTYPE_UNDEFINED)
+        return errorSyntax(USAGE_GUESTCONTROL, "Output conversion not implemented yet!");
 
     /*
@@ -685,30 +699,26 @@
                         *        generic problem and the new VFS APIs will handle it more
                         *        transparently. (requires writing dos2unix/unix2dos filters ofc) */
-                        if (eOutputType != OUTPUTTYPE_UNDEFINED)
+
+                        /*
+                         * If aOutputData is text data from the guest process' stdout or stderr,
+                         * it has a platform dependent line ending. So standardize on
+                         * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
+                         * Windows. Otherwise we end up with CR/CR/LF on Windows.
+                         */
+                        ULONG cbOutputDataPrint = cbOutputData;
+                        for (BYTE *s = aOutputData.raw(), *d = s;
+                             s - aOutputData.raw() < (ssize_t)cbOutputData;
+                             s++, d++)
                         {
-                            /*
-                             * If aOutputData is text data from the guest process' stdout or stderr,
-                             * it has a platform dependent line ending. So standardize on
-                             * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
-                             * Windows. Otherwise we end up with CR/CR/LF on Windows.
-                             */
-                            ULONG cbOutputDataPrint = cbOutputData;
-                            for (BYTE *s = aOutputData.raw(), *d = s;
-                                 s - aOutputData.raw() < (ssize_t)cbOutputData;
-                                 s++, d++)
+                            if (*s == '\r')
                             {
-                                if (*s == '\r')
-                                {
-                                    /* skip over CR, adjust destination */
-                                    d--;
-                                    cbOutputDataPrint--;
-                                }
-                                else if (s != d)
-                                    *d = *s;
+                                /* skip over CR, adjust destination */
+                                d--;
+                                cbOutputDataPrint--;
                             }
-                            RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
+                            else if (s != d)
+                                *d = *s;
                         }
-                        else /* Just dump all data as we got it ... */
-                            RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputData);
+                        RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
                     }
                 }
@@ -790,16 +800,246 @@
 }
 
-/** @todo Clean up too long parameter list -> move guest specific stuff into own struct etc! */
-static int ctrlCopyDirectoryReadGuest(IGuest *pGuest,
-                                      const char *pszUsername, const char *pszPassword,
-                                      const char *pszRootDir, const char *pszSubDir,
-                                      const char *pszFilter, const char *pszDest,
-                                      uint32_t fFlags, uint32_t *pcObjects, DESTDIRMAP &dirMap)
-{
-    AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
+static int ctrlCopyContextCreate(IGuest *pGuest, bool fVerbose, bool fHostToGuest,
+                                 const char *pszUsername, const char *pszPassword,
+                                 PCOPYCONTEXT *ppContext)
+{
+    AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszUsername, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszPassword, VERR_INVALID_POINTER);
+
+    PCOPYCONTEXT pContext = (PCOPYCONTEXT)RTMemAlloc(sizeof(COPYCONTEXT));
+    AssertPtrReturn(pContext, VERR_NO_MEMORY);
+    pContext->pGuest = pGuest;
+    pContext->fVerbose = fVerbose;
+    pContext->fHostToGuest = fHostToGuest;
+
+    pContext->pszUsername = RTStrDup(pszUsername);
+    if (!pContext->pszUsername)
+    {
+        RTMemFree(pContext);
+        return VERR_NO_MEMORY;
+    }
+
+    pContext->pszPassword = RTStrDup(pszPassword);
+    if (!pContext->pszPassword)
+    {
+        RTStrFree(pContext->pszUsername);
+        RTMemFree(pContext);
+        return VERR_NO_MEMORY;
+    }
+
+    *ppContext = pContext;
+
+    return VINF_SUCCESS;
+}
+
+static void ctrlCopyContextFree(PCOPYCONTEXT pContext)
+{
+    if (pContext)
+    {
+        RTStrFree(pContext->pszUsername);
+        RTStrFree(pContext->pszPassword);
+        RTMemFree(pContext);
+    }
+}
+
+/*
+ * Source          Dest                 Translated
+ * c:\foo.txt      c:\from_host         c:\from_host\foo.txt
+ * c:\asdf\foo     c:\qwer              c:\qwer\foo
+ * c:\bar\baz.txt  d:\users\            d:\users\baz.txt
+ * c:\*.dll        e:\baz               e:\baz
+ */
+static int ctrlCopyTranslatePath(PCOPYCONTEXT pContext,
+                                 const char *pszSourceRoot, const char *pszSource,
+                                 const char *pszDest, char **ppszTranslated)
+{
+    AssertPtrReturn(pContext, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
+    AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
+
+    /* Source path must contain the source root! */
+    if (!RTPathStartsWith(pszSource, pszSourceRoot))
+        return VERR_INVALID_PARAMETER;
+
+    /* Construct the relative dest destination path by "subtracting" the
+     * source from the source root, e.g.
+     *
+     * source root path = "e:\foo\", source = "e:\foo\bar"
+     * dest = "d:\baz\"
+     * translated = "d:\baz\bar\"
+     */
+
+    size_t lenRoot = strlen(pszSourceRoot);
+    AssertReturn(lenRoot, VERR_INVALID_PARAMETER);
+    char *pszTranslated = RTStrDup(pszDest);
+    AssertReturn(pszTranslated, VERR_NO_MEMORY);
+    int vrc = RTStrAAppend(&pszTranslated, &pszSource[lenRoot]);
+    if (RT_FAILURE(vrc))
+        return vrc;
+
+    *ppszTranslated = pszTranslated;
+
+#ifdef DEBUG
+    if (pContext->fVerbose)
+        RTPrintf("Translating root=%s, source=%s, dest=%s -> %s\n",
+                 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
+#endif
+
+    return vrc;
+}
+
+static int ctrlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
+{
+    AssertPtrReturn(pContext, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
+
+    int rc = VINF_SUCCESS;
+    if (pContext->fHostToGuest) /* We want to create directories on the guest. */
+    {
+        HRESULT hrc = pContext->pGuest->DirectoryCreate(Bstr(pszDir).raw(),
+                                                        Bstr(pContext->pszUsername).raw(), Bstr(pContext->pszPassword).raw(),
+                                                        700, DirectoryCreateFlag_Parents);
+        if (FAILED(hrc))
+            rc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
+    }
+    else /* ... or on the host. */
+    {
+        rc = RTDirCreate(pszDir, 700);
+        if (rc == VERR_ALREADY_EXISTS)
+            rc = VINF_SUCCESS;
+    }
+    return rc;
+}
+
+static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest,
+                             const char *pszDir, bool *fExists)
+{
+    AssertPtrReturn(pContext, false);
+    AssertPtrReturn(pszDir, false);
+    AssertPtrReturn(fExists, false);
+
+    int rc = VINF_SUCCESS;
+    if (bGuest)
+    {
+        BOOL fDirExists = FALSE;
+        HRESULT hr = pContext->pGuest->FileExists(Bstr(pszDir).raw(),
+                                                  Bstr(pContext->pszUsername).raw(),
+                                                  Bstr(pContext->pszPassword).raw(), &fDirExists);
+        if (FAILED(hr))
+            rc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
+        else
+            *fExists = fDirExists ? true : false;
+    }
+    else
+        *fExists = RTDirExists(pszDir);
+    return rc;
+}
+
+static int ctrlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
+                                   bool *fExists)
+{
+    return ctrlCopyDirExists(pContext, pContext->fHostToGuest,
+                             pszDir, fExists);
+}
+
+static int ctrlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
+                                     bool *fExists)
+{
+    return ctrlCopyDirExists(pContext, !pContext->fHostToGuest,
+                             pszDir, fExists);
+}
+
+static int ctrlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
+                              const char *pszFile, bool *fExists)
+{
+    AssertPtrReturn(pContext, false);
+    AssertPtrReturn(pszFile, false);
+    AssertPtrReturn(fExists, false);
+
+    int rc = VINF_SUCCESS;
+    if (bOnGuest)
+    {
+        BOOL fFileExists = FALSE;
+        HRESULT hr = pContext->pGuest->FileExists(Bstr(pszFile).raw(),
+                                                  Bstr(pContext->pszUsername).raw(),
+                                                  Bstr(pContext->pszPassword).raw(), &fFileExists);
+        if (FAILED(hr))
+            rc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
+        else
+            *fExists = fFileExists ? true : false;
+    }
+    else
+        *fExists = RTFileExists(pszFile);
+    return rc;
+}
+
+static int ctrlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
+                                    bool *fExists)
+{
+    return ctrlCopyFileExists(pContext, pContext->fHostToGuest,
+                              pszFile, fExists);
+}
+
+static int ctrlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
+                                      bool *fExists)
+{
+    return ctrlCopyFileExists(pContext, !pContext->fHostToGuest,
+                              pszFile, fExists);
+}
+
+static int ctrlCopyFileToTarget(PCOPYCONTEXT pContext, const char *pszFileSource,
+                                const char *pszFileDest, uint32_t fFlags)
+{
+    AssertPtrReturn(pContext, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
+
+    if (pContext->fVerbose)
+    {
+        RTPrintf("Copying \"%s\" to \"%s\" ...\n",
+                 pszFileSource, pszFileDest);
+    }
+
+    int vrc = VINF_SUCCESS;
+    ComPtr<IProgress> progress;
+    HRESULT hr;
+    if (pContext->fHostToGuest)
+    {
+        hr = pContext->pGuest->CopyToGuest(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
+                                           Bstr(pContext->pszUsername).raw(), Bstr(pContext->pszPassword).raw(),
+                                           fFlags, progress.asOutParam());
+    }
+    else
+    {
+        hr = pContext->pGuest->CopyFromGuest(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
+                                             Bstr(pContext->pszUsername).raw(), Bstr(pContext->pszPassword).raw(),
+                                             fFlags, progress.asOutParam());
+    }
+
+    if (FAILED(hr))
+        vrc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
+    else
+    {
+        hr = showProgress(progress);
+        if (FAILED(hr))
+            vrc = ctrlPrintProgressError(progress);
+    }
+
+    return vrc;
+}
+
+static int ctrlCopyDirToGuest(PCOPYCONTEXT pContext,
+                              const char *pszSource, const char *pszFilter,
+                              const char *pszDest, uint32_t fFlags,
+                              const char *pszSubDir /* For recursion */)
+{
+    AssertPtrReturn(pContext, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
+    /* Filter is optional. */
+    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
     /* Sub directory is optional. */
-    /* Filter directory is optional. */
-    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
 
     /*
@@ -807,128 +1047,11 @@
      */
     char szCurDir[RTPATH_MAX];
-    int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszRootDir);
-    if (RT_SUCCESS(rc) && pszSubDir != NULL)
+    int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
+    if (RT_SUCCESS(rc) && pszSubDir)
         rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
 
-    if (RT_SUCCESS(rc))
-    {
-        ULONG uDirHandle;
-        HRESULT hr = pGuest->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(), fFlags,
-                                           Bstr(pszUsername).raw(), Bstr(pszPassword).raw(), &uDirHandle);
-        if (FAILED(hr))
-            rc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
-        else
-        {
-            ComPtr <IGuestDirEntry> dirEntry;
-            while (SUCCEEDED(hr = pGuest->DirectoryRead(uDirHandle, dirEntry.asOutParam())))
-            {
-                GuestDirEntryType_T enmType;
-                dirEntry->COMGETTER(Type)(&enmType);
-
-                Bstr strName;
-                dirEntry->COMGETTER(Name)(strName.asOutParam());
-
-                switch (enmType)
-                {
-                    case GuestDirEntryType_Directory:
-                    {
-                        /* Skip "." and ".." entries. */
-                        if (   !strName.compare(Bstr("."))
-                            || !strName.compare(Bstr("..")))
-                            break;
-
-                        const char *pszName = Utf8Str(strName).c_str();
-                        if (fFlags & CopyFileFlag_Recursive)
-                        {
-                            char *pszNewSub = NULL;
-                            if (pszSubDir)
-                                RTStrAPrintf(&pszNewSub, "%s/%s", pszSubDir, pszName);
-                            else
-                                RTStrAPrintf(&pszNewSub, "%s", pszName);
-
-                            if (pszNewSub)
-                            {
-                                dirMap[pszNewSub];
-
-                                rc = ctrlCopyDirectoryReadGuest(pGuest, pszUsername, pszPassword,
-                                                                pszRootDir, pszNewSub,
-                                                                pszFilter, pszDest,
-                                                                fFlags, pcObjects, dirMap);
-                                RTStrFree(pszNewSub);
-                            }
-                            else
-                                rc = VERR_NO_MEMORY;
-                        }
-                        break;
-                    }
-
-                    case GuestDirEntryType_Symlink:
-                        if (   (fFlags & CopyFileFlag_Recursive)
-                            && (fFlags & CopyFileFlag_FollowLinks))
-                        {
-                            /* Fall through to next case is intentional. */
-                        }
-                        else
-                            break;
-
-                    case GuestDirEntryType_File:
-                    {
-                        const char *pszName = Utf8Str(strName).c_str();
-                        if (   !pszFilter
-                            || RTStrSimplePatternMatch(pszFilter, pszName))
-                        {
-                            dirMap[pszSubDir].push_back(DESTFILEENTRY(pszName));
-                            *pcObjects += 1;
-                        }
-                        break;
-                    }
-
-                    default:
-                        break;
-                }
-            }
-
-            hr = pGuest->DirectoryClose(uDirHandle);
-            if (FAILED(rc))
-                rc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
-        }
-    }
-    return rc;
-}
-
-/**
- * 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   fFlags              Copy flags.
- * @param   pcObjects           Where to store the overall objects to
- *                              copy found.
- * @param   dirMap              Reference to destination directory map to store found
- *                              directories (primary key) + files (secondary key, vector).
- */
-static int ctrlCopyDirectoryReadHost(const char *pszRootDir, const char *pszSubDir,
-                                     const char *pszFilter, const char *pszDest,
-                                     uint32_t fFlags, uint32_t *pcObjects, DESTDIRMAP &dirMap)
-{
-    AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
-    /* Sub directory is optional. */
-    /* Filter directory is optional. */
-    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
-
-    /*
-     * Construct current path.
-     */
-    char szCurDir[RTPATH_MAX];
-    int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszRootDir);
-    if (RT_SUCCESS(rc) && pszSubDir != NULL)
-        rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
+    /* Flag indicating whether the current directory was created on the
+     * target or not. */
+    bool fDirCreated = false;
 
     /*
@@ -977,9 +1100,7 @@
                         if (pszNewSub)
                         {
-                            dirMap[pszNewSub];
-
-                            rc = ctrlCopyDirectoryReadHost(pszRootDir, pszNewSub,
-                                                           pszFilter, pszDest,
-                                                           fFlags, pcObjects, dirMap);
+                            rc = ctrlCopyDirToGuest(pContext,
+                                                    pszSource, pszFilter,
+                                                    pszDest, fFlags, pszNewSub);
                             RTStrFree(pszNewSub);
                         }
@@ -1004,6 +1125,36 @@
                         || RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
                     {
-                        dirMap[pszSubDir].push_back(DESTFILEENTRY(Utf8Str(DirEntry.szName)));
-                        *pcObjects += 1;
+                        if (!fDirCreated)
+                        {
+                            char *pszDestDir;
+                            rc = ctrlCopyTranslatePath(pContext, pszSource, szCurDir,
+                                                       pszDest, &pszDestDir);
+                            if (RT_SUCCESS(rc))
+                            {
+                                rc = ctrlCopyDirCreate(pContext, pszDestDir);
+                                RTStrFree(pszDestDir);
+
+                                fDirCreated = true;
+                            }
+                        }
+
+                        if (RT_SUCCESS(rc))
+                        {
+                            char *pszFileSource;
+                            if (RTStrAPrintf(&pszFileSource, "%s/%s",
+                                             szCurDir, DirEntry.szName))
+                            {
+                                char *pszFileDest;
+                                rc = ctrlCopyTranslatePath(pContext, pszSource, pszFileSource,
+                                                           pszDest, &pszFileDest);
+                                if (RT_SUCCESS(rc))
+                                {
+                                    rc = ctrlCopyFileToTarget(pContext, pszFileSource,
+                                                              pszFileDest, 0 /* Flags? */);
+                                    RTStrFree(pszFileDest);
+                                }
+                                RTStrFree(pszFileSource);
+                            }
+                        }
                     }
                     break;
@@ -1022,265 +1173,188 @@
 }
 
-/**
- * Constructs a destinations map from a source entry and a destination root.
- *
- * @return  IPRT status code.
- * @param   fHostToGuest
- * @param   sourceEntry             Reference to a specified source entry to use.
- * @param   fFlags                  Copy file flags. Needed for recursive directory parsing.
- * @param   pszDestRoot             Pointer to destination root. This can be used to add one or
- *                                  more directories to the actual destination path.
- * @param   mapDest                 Reference to the destination map for storing the actual result.
- * @param   pcObjects               Pointer to a total object (file) count to copy.
- */
-static int ctrlCopyConstructDestinationsForGuest(SOURCEFILEENTRY &sourceEntry, uint32_t fFlags,
-                                                 const char *pszDestRoot, DESTDIRMAP &mapDest,
-                                                 uint32_t *pcObjects)
-{
-    int rc = VINF_SUCCESS;
-    const char *pszSource = sourceEntry.mSource.c_str();
-
-    if (   RTPathFilename(pszSource)
-        && RTFileExists(pszSource))
-    {
-        /* Source is a single file. */
-        char *pszFileName = RTPathFilename(pszSource);
-        mapDest[Utf8Str("")].push_back(DESTFILEENTRY(pszFileName));
-
-        *pcObjects += 1;
-    }
+static int ctrlCopyDirToHost(PCOPYCONTEXT pContext,
+                             const char *pszSource, const char *pszFilter,
+                             const char *pszDest, uint32_t fFlags,
+                             const char *pszSubDir /* For recursion */)
+{
+    AssertPtrReturn(pContext, VERR_INVALID_POINTER);
+    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
+    /* Filter is optional. */
+    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
+    /* Sub directory is optional. */
+
+    /*
+     * Construct current path.
+     */
+    char szCurDir[RTPATH_MAX];
+    int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
+    if (RT_SUCCESS(rc) && pszSubDir)
+        rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
+
+    /* Flag indicating whether the current directory was created on the
+     * target or not. */
+    bool fDirCreated = false;
+
+    ULONG uDirHandle;
+    HRESULT hr = pContext->pGuest->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
+                                                 fFlags,
+                                                 Bstr(pContext->pszUsername).raw(), Bstr(pContext->pszPassword).raw(),
+                                                 &uDirHandle);
+    if (FAILED(hr))
+        rc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
     else
     {
-        /* Source is either a directory or a filter (e.g. *.dll). */
-        rc = ctrlCopyDirectoryReadHost(pszSource,
-                                       NULL /* pszSubDir */,
-                                       sourceEntry.mFilter.isEmpty() ? NULL : sourceEntry.mFilter.c_str(),
-                                       pszDestRoot, fFlags, pcObjects, mapDest);
-    }
+        ComPtr <IGuestDirEntry> dirEntry;
+        while (SUCCEEDED(hr = pContext->pGuest->DirectoryRead(uDirHandle, dirEntry.asOutParam())))
+        {
+            GuestDirEntryType_T enmType;
+            dirEntry->COMGETTER(Type)(&enmType);
+
+            Bstr strName;
+            dirEntry->COMGETTER(Name)(strName.asOutParam());
+
+            switch (enmType)
+            {
+                case GuestDirEntryType_Directory:
+                {
+                    /* Skip "." and ".." entries. */
+                    if (   !strName.compare(Bstr("."))
+                        || !strName.compare(Bstr("..")))
+                        break;
+
+                    if (fFlags & CopyFileFlag_Recursive)
+                    {
+                        Utf8Str strDir(strName);
+                        char *pszNewSub = NULL;
+                        if (pszSubDir)
+                            RTStrAPrintf(&pszNewSub, "%s/%s", pszSubDir, strDir.c_str());
+                        else
+                            RTStrAPrintf(&pszNewSub, "%s", strDir.c_str());
+
+                        if (pszNewSub)
+                        {
+                            rc = ctrlCopyDirToHost(pContext,
+                                                   pszSource, pszFilter,
+                                                   pszDest, fFlags, pszNewSub);
+                            RTStrFree(pszNewSub);
+                        }
+                        else
+                            rc = VERR_NO_MEMORY;
+                    }
+                    break;
+                }
+
+                case GuestDirEntryType_Symlink:
+                    if (   (fFlags & CopyFileFlag_Recursive)
+                        && (fFlags & CopyFileFlag_FollowLinks))
+                    {
+                        /* Fall through to next case is intentional. */
+                    }
+                    else
+                        break;
+
+                case GuestDirEntryType_File:
+                {
+                    const char *pszName = Utf8Str(strName).c_str();
+                    if (   !pszFilter
+                        || RTStrSimplePatternMatch(pszFilter, pszName))
+                    {
+                        if (!fDirCreated)
+                        {
+                            char *pszDestDir;
+                            rc = ctrlCopyTranslatePath(pContext, pszSource, szCurDir,
+                                                       pszDest, &pszDestDir);
+                            if (RT_SUCCESS(rc))
+                            {
+                                rc = ctrlCopyDirCreate(pContext, pszDestDir);
+                                RTStrFree(pszDestDir);
+
+                                fDirCreated = true;
+                            }
+                        }
+
+                        if (RT_SUCCESS(rc))
+                        {
+                            Utf8Str strDir(strName);
+                            char *pszFileSource;
+                            if (RTStrAPrintf(&pszFileSource, "%s/%s",
+                                             szCurDir, strDir.c_str()))
+                            {
+                                char *pszFileDest;
+                                rc = ctrlCopyTranslatePath(pContext, pszSource, pszFileSource,
+                                                           pszDest, &pszFileDest);
+                                if (RT_SUCCESS(rc))
+                                {
+                                    rc = ctrlCopyFileToTarget(pContext, pszFileSource,
+                                                              pszFileDest, 0 /* Flags? */);
+                                    RTStrFree(pszFileDest);
+                                }
+                                RTStrFree(pszFileSource);
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                default:
+                    break;
+            }
+        }
+
+        hr = pContext->pGuest->DirectoryClose(uDirHandle);
+        if (FAILED(rc))
+            rc = ctrlPrintError(pContext->pGuest, COM_IIDOF(IGuest));
+    }
+
     return rc;
 }
 
-static int ctrlCopyConstructDestinationsForHost(IGuest *pGuest,
-                                                const char *pszUsername, const char *pszPassword,
-                                                SOURCEFILEENTRY &sourceEntry, uint32_t fFlags,
-                                                const char *pszDestRoot, DESTDIRMAP &mapDest,
-                                                uint32_t *pcObjects)
-{
-    int rc = VINF_SUCCESS;
-    const char *pszSource = sourceEntry.mSource.c_str();
-
-    BOOL fExists = FALSE;
-    HRESULT hr = pGuest->FileExists(Bstr(pszSource).raw(),
-                                    Bstr(pszUsername).raw(), Bstr(pszPassword).raw(), &fExists);
-    if (FAILED(rc))
-        rc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
+static int ctrlCopyDirToTarget(PCOPYCONTEXT pContext,
+                               const char *pszSource, const char *pszFilter,
+                               const char *pszDest, uint32_t fFlags,
+                               const char *pszSubDir /* For recursion */)
+{
+    if (pContext->fHostToGuest)
+        return ctrlCopyDirToGuest(pContext, pszSource, pszFilter,
+                                  pszDest, fFlags, pszSubDir);
+    return ctrlCopyDirToHost(pContext, pszSource, pszFilter,
+                             pszDest, fFlags, pszSubDir);
+}
+
+static int ctrlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
+{
+    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
+    AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
+
+    char *pszNewRoot = RTStrDup(pszSource);
+    AssertPtrReturn(pszNewRoot, VERR_NO_MEMORY);
+
+    size_t lenRoot = strlen(pszNewRoot);
+    if (   lenRoot
+        && pszNewRoot[lenRoot - 1] == '/'
+        && pszNewRoot[lenRoot - 1] == '\\'
+        && lenRoot > 1
+        && pszNewRoot[lenRoot - 2] == '/'
+        && pszNewRoot[lenRoot - 2] == '\\')
+    {
+        *ppszSourceRoot = pszNewRoot;
+        if (lenRoot > 1)
+            *ppszSourceRoot[lenRoot - 2] = '\0';
+        *ppszSourceRoot[lenRoot - 1] = '\0';
+    }
     else
     {
-        if (fExists)
-        {
-            /* Source is a single file. */
-            char *pszFileName = RTPathFilename(pszSource);
-            mapDest[Utf8Str(pszDestRoot)].push_back(DESTFILEENTRY(pszFileName));
-
-            *pcObjects++;
-        }
-        else
-        {
-            /* Source is either a directory or a filter (e.g. *.dll). */
-            rc = ctrlCopyDirectoryReadGuest(pGuest, pszUsername, pszPassword,
-                                            pszSource, NULL /* pszSubDir */,
-                                            sourceEntry.mFilter.isEmpty() ? NULL : sourceEntry.mFilter.c_str(),
-                                            pszDestRoot, fFlags, pcObjects, mapDest);
-        }
-    }
-    return rc;
-}
-
-/**
- * Prepares the destination directory hirarchy on the guest side by creating the directories
- * and sets the appropriate access rights.
- *
- * @return  IPRT status code.
- * @param   pGuest                  IGuest interface pointer.
- * @param   fHostToGuest
- * @param   itDest                  Destination map iterator to process.
- * @param   pszDestRoot             Destination root to use.
- * @param   pszUsername             Username to use.
- * @param   pszPassword             Password to use.
- */
-static int ctrlCopyPrepareDestDirectory(IGuest *pGuest, bool fHostToGuest,
-                                        const char *pszDestRoot, const char *pszDestSub,
-                                        const char *pszUsername, const char *pszPassword)
-{
-    AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDestRoot, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDestSub, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszUsername, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszPassword, VERR_INVALID_POINTER);
-
-    char *pszDestFinal = NULL;
-    int rc = VINF_SUCCESS;
-
-    /* Create root directory (= empty name) and skip the rest for
-     * this round. */
-    if (!strlen(pszDestSub))
-    {
-        pszDestFinal = RTStrDup(pszDestRoot);
-        if (!pszDestFinal)
-            rc = VERR_NO_MEMORY;
-    }
-    else /* Create sub-directories, also empty ones. */
-    {
-        if (!RTStrAPrintf(&pszDestFinal, "%s/%s", pszDestRoot, pszDestSub))
-            rc = VERR_NO_MEMORY;
-    }
-
-    if (RT_SUCCESS(rc) && pszDestFinal)
-    {
-        if (fHostToGuest) /* We want to create directories on the guest. */
-        {
-            HRESULT hrc = pGuest->DirectoryCreate(Bstr(pszDestFinal).raw(),
-                                                  Bstr(pszUsername).raw(), Bstr(pszPassword).raw(),
-                                                  700, DirectoryCreateFlag_Parents);
-            if (FAILED(hrc))
-                rc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
-        }
-        else /* ... or on the host. */
-        {
-            rc = RTDirCreate(pszDestFinal, 700);
-        }
-        RTStrFree(pszDestFinal);
-    }
-    return rc;
-}
-
-/**
- * Copys a file from host to the guest.
- *
- * @return  IPRT status code.
- * @param   pGuest                  IGuest interface pointer.
- * @param   pszSource               Source path of existing host file to copy to the guest.
- * @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   fFlags                  Copy flags.
- */
-static int ctrlCopyFileToGuest(IGuest *pGuest, const char *pszSource, const char *pszDest,
-                               const char *pszUserName, const char *pszPassword,
-                               uint32_t fFlags)
-{
-    AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszUserName, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszPassword, VERR_INVALID_POINTER);
-
-    int vrc = VINF_SUCCESS;
-    ComPtr<IProgress> progress;
-    HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
-                                     Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
-                                     fFlags, progress.asOutParam());
-    if (FAILED(rc))
-        vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
-    else
-    {
-        rc = showProgress(progress);
-        if (FAILED(rc))
-            vrc = ctrlPrintProgressError(progress);
-    }
-    return vrc;
-}
-
-/**
- * Copys a file from guest to the host.
- *
- * @return  IPRT status code.
- * @param   pGuest                  IGuest interface pointer.
- * @param   pszSource               Source path of existing guest file to copy to the host.
- * @param   pszDest                 Destination path/file on host to copy the file to.
- * @param   pszUserName             User name on guest to use for the copy operation.
- * @param   pszPassword             Password of user account.
- * @param   fFlags                  Copy flags.
- */
-static int ctrlCopyFileToHost(IGuest *pGuest, const char *pszSource, const char *pszDest,
-                              const char *pszUserName, const char *pszPassword,
-                              uint32_t fFlags)
-{
-    AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszUserName, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszPassword, VERR_INVALID_POINTER);
-
-    int vrc = VINF_SUCCESS;
-    ComPtr<IProgress> progress;
-    HRESULT rc = pGuest->CopyFromGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
-                                       Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
-                                       fFlags, progress.asOutParam());
-    if (FAILED(rc))
-        vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
-    else
-    {
-        rc = showProgress(progress);
-        if (FAILED(rc))
-            vrc = ctrlPrintProgressError(progress);
-    }
-    return vrc;
-}
-
-static int ctrlCopyToDestDirectory(IGuest *pGuest, bool fVerbose, bool fDryRun, bool fHostToGuest,
-                                   const char *pszSourceDir,
-                                   const char *pszDestRoot, const char *pszDestSub, const char *pszFileName,
-                                   uint32_t uFlags, const char *pszUsername, const char *pszPassword)
-{
-    AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDestRoot, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszDestSub, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszSourceDir, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszUsername, VERR_INVALID_POINTER);
-    AssertPtrReturn(pszPassword, VERR_INVALID_POINTER);
-
-    int iLen;
-    char *pszSource;
-    if (!strlen(pszDestSub))
-        iLen = RTStrAPrintf(&pszSource, "%s/%s", pszSourceDir, pszFileName);
-    else
-        iLen = RTStrAPrintf(&pszSource, "%s/%s/%s",
-                            pszSourceDir, pszDestSub, pszFileName);
-    if (!iLen)
-        return VERR_NO_MEMORY;
-
-    char *pszDest;
-    if (!strlen(pszDestSub))
-        iLen = RTStrAPrintf(&pszDest, "%s/%s", pszDestRoot, pszFileName);
-    else
-        iLen = RTStrAPrintf(&pszDest, "%s/%s/%s", pszDestRoot, pszDestSub,
-                            pszFileName);
-    if (!iLen)
-    {
-        RTStrFree(pszSource);
-        return VERR_NO_MEMORY;
-    }
-
-    if (fVerbose)
-        RTPrintf("\"%s\" -> \"%s\"\n", pszSource, pszDest);
-
-    int rc = VINF_SUCCESS;
-
-    /* Finally copy the desired file (if no dry run selected). */
-    if (!fDryRun)
-    {
-        if (fHostToGuest)
-            rc = ctrlCopyFileToGuest(pGuest, pszSource, pszDest,
-                                     pszUsername, pszPassword, uFlags);
-        else
-            rc = ctrlCopyFileToHost(pGuest, pszSource, pszDest,
-                                    pszUsername, pszPassword, uFlags);
-    }
-    RTStrFree(pszSource);
-    RTStrFree(pszDest);
-
-    return rc;
+        /* If there's anything (like a file name or a filter),
+         * strip it! */
+        RTPathStripFilename(pszNewRoot);
+        *ppszSourceRoot = pszNewRoot;
+    }
+
+    return VINF_SUCCESS;
+}
+
+static void ctrlCopyFreeSourceRoot(char *pszSourceRoot)
+{
+    RTStrFree(pszSourceRoot);
 }
 
@@ -1415,116 +1489,108 @@
     }
 
-    /* Strip traling slash from destination path. */
-    RTPathStripTrailingSlash(Utf8Dest.mutableRaw());
-    Utf8Dest.jolt();
-
-    /*
-     * Here starts the actual fun!
-     */
-    for (unsigned long s = 0; s < vecSources.size(); s++)
-    {
-        char *pszSourceDir;
-        if (RTDirExists(vecSources[s].mSource.c_str()))
-            pszSourceDir = RTStrDup(vecSources[s].mSource.c_str());
-        else
-        {
-            pszSourceDir = RTStrDup(vecSources[s].mSource.c_str());
-            RTPathStripFilename(pszSourceDir);
-        }
-
-        uint32_t cObjects = 0;
-        DESTDIRMAP mapDest;
-        const char *pszDestRoot = Utf8Dest.c_str();
-
-        if (fHostToGuest)
-            vrc = ctrlCopyConstructDestinationsForGuest(vecSources[s], fFlags, pszDestRoot,
-                                                        mapDest, &cObjects);
-        else
-            vrc = ctrlCopyConstructDestinationsForHost(guest, Utf8UserName.c_str(), Utf8Password.c_str(),
-                                                       vecSources[s], fFlags, pszDestRoot,
-                                                       mapDest, &cObjects);
-        if (RT_FAILURE(vrc))
-        {
-            if (   fVerbose
-                && vrc == VERR_FILE_NOT_FOUND)
-            {
-                RTPrintf("Warning: Source \"%s\" does not exist, skipping!\n",
-                         vecSources[s].mSource.c_str());
-            }
-        }
-        else
-        {
-            /*
-             * Prepare directory structure of each destination directory.
-             */
-            DESTDIRMAPITER itDest;
-            for (itDest = mapDest.begin(); itDest != mapDest.end(); itDest++)
-            {
-                if (fVerbose)
+    /* Create the copy context -- it contains all information
+     * the routines need to know when handling the actual copying. */
+    PCOPYCONTEXT pContext;
+    vrc = ctrlCopyContextCreate(guest, fVerbose, fHostToGuest,
+                                Utf8UserName.c_str(), Utf8Password.c_str(),
+                                &pContext);
+    if (RT_FAILURE(vrc))
+    {
+        RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
+        return RTEXITCODE_FAILURE;
+    }
+
+    /* If the destination is a path, (try to) create it. */
+    const char *pszDest = Utf8Dest.c_str();
+    AssertPtr(pszDest);
+    size_t lenDest = strlen(pszDest);
+    if (   lenDest
+         ||pszDest[lenDest - 1] == '/'
+        || pszDest[lenDest - 1] == '\\')
+    {
+        vrc = ctrlCopyDirCreate(pContext, pszDest);
+    }
+
+    if (RT_SUCCESS(vrc))
+    {
+        /*
+         * Here starts the actual fun!
+         * Handle all given sources one by one.
+         */
+        for (unsigned long s = 0; s < vecSources.size(); s++)
+        {
+            const char *pszSource = vecSources[s].mSource.c_str();
+            const char *pszFilter = vecSources[s].mFilter.c_str();
+
+            char *pszSourceRoot;
+            vrc = ctrlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
+            if (RT_FAILURE(vrc))
+            {
+                RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
+                break;
+            }
+
+            if (fVerbose)
+                RTPrintf("Source: %s\n", pszSource);
+
+            /* @todo Files with filter?? */
+            bool fExists;
+            vrc = ctrlCopyFileExistsOnSource(pContext, pszSource, &fExists);
+            if (RT_SUCCESS(vrc))
+            {
+                if (fExists)
                 {
-                    const char *pszSubDir = itDest->first.c_str();
-                    AssertPtr(pszSubDir);
-                    if (!strlen(pszSubDir))
-                        RTPrintf("Preparing directory \"%s\" ...\n", pszDestRoot);
+                    /* Single file. */
+                    char *pszDest;
+                    vrc = ctrlCopyTranslatePath(pContext, pszSourceRoot, pszSource,
+                                                Utf8Dest.c_str(), &pszDest);
+                    if (RT_SUCCESS(vrc))
+                    {
+                        vrc = ctrlCopyFileToTarget(pContext, pszSource,
+                                                   pszDest, fFlags);
+                        RTStrFree(pszDest);
+                    }
                     else
-                        RTPrintf("Preparing directory \"%s/%s\" ...\n", pszDestRoot,
-                                 itDest->first.c_str());
+                    {
+                        RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n",
+                                   pszSource, vrc);
+                    }
                 }
-                if (!fDryRun)
-                    vrc = ctrlCopyPrepareDestDirectory(guest, fHostToGuest,
-                                                       pszDestRoot, itDest->first.c_str(),
-                                                       Utf8UserName.c_str(), Utf8Password.c_str());
-                if (RT_FAILURE(vrc))
-                    break;
+                else
+                {
+                    if (   (RT_SUCCESS(ctrlCopyDirExistsOnSource(pContext, pszSource, &fExists))
+                            && fExists)
+                        || (   RT_SUCCESS(ctrlCopyDirExistsOnSource(pContext, pszSourceRoot, &fExists))
+                            && fExists
+                            && pszFilter)
+                       )
+                    {
+                        /* Directory (with filter?). */
+                        vrc = ctrlCopyDirToTarget(pContext, pszSource, pszFilter,
+                                                  Utf8Dest.c_str(), fFlags, NULL /* Subdir */);
+                    }
+                }
+            }
+
+            ctrlCopyFreeSourceRoot(pszSourceRoot);
+
+            if (   RT_SUCCESS(vrc)
+                && !fExists)
+            {
+                RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
+                           pszSource);
+                continue;
             }
 
             if (RT_FAILURE(vrc))
-                break;
-
-            if (fVerbose)
-            {
-                if (!cObjects)
-                    RTPrintf("Warning: Source \"%s\" has no (matching) files to copy, skipping!\n",
-                             vecSources[s].mSource.c_str());
-                else
-                    RTPrintf("Copying \"%s\" (%u files) ...\n",
-                             vecSources[s].mSource.c_str(), cObjects);
-            }
-
-            /*
-             * Copy files of each destination root directory to the guest.
-             */
-            for (itDest = mapDest.begin(); itDest != mapDest.end(); itDest++)
-            {
-                if (fVerbose && itDest->second.size())
-                {
-                    if (itDest->first.isEmpty())
-                        RTPrintf("Copying %u files ...\n", itDest->second.size());
-                    else
-                        RTPrintf("Copying directory \"%s\" (%u files) ...\n",
-                                 itDest->first.c_str(), itDest->second.size());
-                }
-
-                for (unsigned long l = 0; l < itDest->second.size(); l++)
-                {
-                    vrc = ctrlCopyToDestDirectory(guest, fVerbose, fDryRun, fHostToGuest,
-                                                  pszSourceDir,
-                                                  pszDestRoot, itDest->first.c_str() /* Sub directory */,
-                                                  itDest->second[l].mFileName.c_str() /* Filename */,
-                                                  fFlags, Utf8UserName.c_str(), Utf8Password.c_str());
-                    if (RT_FAILURE(vrc))
-                        break;
-                }
-
-                if (RT_FAILURE(vrc))
-                    break;
-            }
-
-            if (RT_FAILURE(vrc))
-                break;
-        }
-
-        RTStrFree(pszSourceDir);
-    }
+            {
+                RTMsgError("Error processing \"%s\", rc=%Rrc\n",
+                           pszSource, vrc);
+                break;
+            }
+        }
+    }
+
+    ctrlCopyContextFree(pContext);
 
     return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
@@ -1632,4 +1698,119 @@
                 ctrlPrintError(guest, COM_IIDOF(IGuest)); /* Return code ignored, save original rc. */
                 break;
+            }
+
+            it++;
+        }
+
+        if (FAILED(hrc))
+            rcExit = RTEXITCODE_FAILURE;
+    }
+
+    return rcExit;
+}
+
+static int handleCtrlStat(ComPtr<IGuest> guest, HandlerArg *pArg)
+{
+    AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
+
+    /*
+     * Parse arguments.
+     *
+     * Note! No direct returns here, everyone must go thru the cleanup at the
+     *       end of this function.
+     */
+    static const RTGETOPTDEF s_aOptions[] =
+    {
+        /** @todo Implement "--dereference/-L", and "--file-system/-f" later! */
+        { "--password",            'p',         RTGETOPT_REQ_STRING  },
+        { "--username",            'u',         RTGETOPT_REQ_STRING  },
+        { "--verbose",             'v',         RTGETOPT_REQ_NOTHING }
+    };
+
+    int ch;
+    RTGETOPTUNION ValueUnion;
+    RTGETOPTSTATE GetState;
+    RTGetOptInit(&GetState, pArg->argc, pArg->argv,
+                 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+    Utf8Str Utf8UserName;
+    Utf8Str Utf8Password;
+
+    bool fVerbose = false;
+    DESTDIRMAP mapDirs;
+
+    RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+    while (   (ch = RTGetOpt(&GetState, &ValueUnion))
+           && rcExit == RTEXITCODE_SUCCESS)
+    {
+        /* For options that require an argument, ValueUnion has received the value. */
+        switch (ch)
+        {
+            case 'p': /* Password */
+                Utf8Password = ValueUnion.psz;
+                break;
+
+            case 'u': /* User name */
+                Utf8UserName = ValueUnion.psz;
+                break;
+
+            case 'v': /* Verbose */
+                fVerbose = true;
+                break;
+
+            case VINF_GETOPT_NOT_OPTION:
+            {
+                mapDirs[ValueUnion.psz]; /* Add element to check to map. */
+                break;
+            }
+
+            default:
+                rcExit = RTGetOptPrintError(ch, &ValueUnion);
+                break;
+        }
+    }
+
+    uint32_t cDirs = mapDirs.size();
+    if (rcExit == RTEXITCODE_SUCCESS && !cDirs)
+        rcExit = errorSyntax(USAGE_GUESTCONTROL, "No element to check specified!");
+
+    if (rcExit == RTEXITCODE_SUCCESS && Utf8UserName.isEmpty())
+        rcExit = errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
+
+    if (rcExit == RTEXITCODE_SUCCESS)
+    {
+        /*
+         * Create the directories.
+         */
+        HRESULT hrc = S_OK;
+
+        DESTDIRMAPITER it = mapDirs.begin();
+        while (it != mapDirs.end())
+        {
+            if (fVerbose)
+                RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
+
+            BOOL fExists;
+            hrc = guest->FileExists(Bstr(it->first).raw(),
+                                    Bstr(Utf8UserName).raw(), Bstr(Utf8Password).raw(),
+                                    &fExists);
+            if (FAILED(hrc))
+            {
+                ctrlPrintError(guest, COM_IIDOF(IGuest)); /* Return code ignored, save original rc. */
+                break;
+            }
+            else
+            {
+                /** @todo: Output vbox_stat's stdout output to get more information about
+                 *         what happened. */
+
+                /* If there's at least one element which does not exist on the guest,
+                 * drop out with exitcode 1. */
+                if (!fExists)
+                {
+                    RTPrintf("Cannot stat for element \"%s\": No such file or directory.\n",
+                             it->first.c_str());
+                    rcExit = RTEXITCODE_FAILURE;
+                }
             }
 
@@ -1772,5 +1953,4 @@
             rcExit = handleCtrlExecProgram(guest, &arg);
         }
-#if 0
         else if (!strcmp(pArg->argv[1], "copyfrom"))
         {
@@ -1778,5 +1958,4 @@
                                       false /* Guest to host */);
         }
-#endif
         else if (   !strcmp(pArg->argv[1], "copyto")
                  || !strcmp(pArg->argv[1], "cp"))
@@ -1792,4 +1971,8 @@
             rcExit = handleCtrlCreateDirectory(guest, &arg);
         }
+        else if (   !strcmp(pArg->argv[1], "stat"))
+        {
+            rcExit = handleCtrlStat(guest, &arg);
+        }
         else if (   !strcmp(pArg->argv[1], "updateadditions")
                  || !strcmp(pArg->argv[1], "updateadds"))
