/* $Id: VBoxControl.cpp $ */
/** @file
 * VBoxControl - Guest Additions Command Line Management Interface.
 */

/*
 * Copyright (C) 2008-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/alloca.h>
#include <iprt/cpp/autores.h>
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
#include <iprt/mem.h>
#include <iprt/message.h>
#include <iprt/path.h>
#include <iprt/string.h>
#include <iprt/stream.h>
#include <VBox/log.h>
#include <VBox/version.h>
#include <VBox/VBoxGuestLib.h>
#ifdef RT_OS_WINDOWS
# include <Windows.h>
#endif
#ifdef VBOX_WITH_GUEST_PROPS
# include <VBox/HostServices/GuestPropertySvc.h>
#endif
#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
# include <VBox/VBoxGuest.h>
# include "../VBoxGuestLib/VBGLR3Internal.h" /* HACK ALERT! Using vbglR3DoIOCtl directly!! */
#endif


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** The program name (derived from argv[0]). */
char const *g_pszProgName = "";
/** The current verbosity level. */
int g_cVerbosity = 0;


/**
 * Displays the program usage message.
 *
 * @param u64Which
 *
 * @{
 */

/** Helper function */
static void doUsage(char const *line, char const *name = "", char const *command = "")
{
    /* Allow for up to 15 characters command name length (VBoxControl.exe) with
     * perfect column alignment. Beyond that there's at least one space between
     * the command if there are command line parameters. */
    RTPrintf("%s %-*s%s%s\n", name, strlen(line) ? 35 - strlen(name) : 1,
                              command, strlen(line) ? " " : "", line);
}

/** Enumerate the different parts of the usage we might want to print out */
enum VBoxControlUsage
{
#ifdef RT_OS_WINDOWS
    GET_VIDEO_ACCEL,
    SET_VIDEO_ACCEL,
    VIDEO_FLAGS,
    LIST_CUST_MODES,
    ADD_CUST_MODE,
    REMOVE_CUST_MODE,
    SET_VIDEO_MODE,
#endif
#ifdef VBOX_WITH_GUEST_PROPS
    GUEST_PROP,
#endif
#ifdef VBOX_WITH_SHARED_FOLDERS
    GUEST_SHAREDFOLDERS,
#endif
#if !defined(VBOX_CONTROL_TEST)
    WRITE_CORE_DUMP,
#endif
    TAKE_SNAPSHOT,
    SAVE_STATE,
    SUSPEND,
    POWER_OFF,
    VERSION,
    HELP,
    USAGE_ALL = UINT32_MAX
};

static void usage(enum VBoxControlUsage eWhich = USAGE_ALL)
{
    RTPrintf("Usage:\n\n");
    doUsage("print version number and exit", g_pszProgName, "[-v|--version]");
    doUsage("suppress the logo", g_pszProgName, "--nologo ...");
    RTPrintf("\n");

    /* Exclude the Windows bits from the test version.  Anyone who needs to
       test them can fix this. */
#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)
    if (eWhich  == GET_VIDEO_ACCEL || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "getvideoacceleration");
    if (eWhich  == SET_VIDEO_ACCEL || eWhich == USAGE_ALL)
        doUsage("<on|off>", g_pszProgName, "setvideoacceleration");
    if (eWhich  == VIDEO_FLAGS || eWhich == USAGE_ALL)
        doUsage("<get|set|clear|delete> [hex mask]", g_pszProgName, "videoflags");
    if (eWhich  == LIST_CUST_MODES || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "listcustommodes");
    if (eWhich  == ADD_CUST_MODE || eWhich == USAGE_ALL)
        doUsage("<width> <height> <bpp>", g_pszProgName, "addcustommode");
    if (eWhich  == REMOVE_CUST_MODE || eWhich == USAGE_ALL)
        doUsage("<width> <height> <bpp>", g_pszProgName, "removecustommode");
    if (eWhich  == SET_VIDEO_MODE || eWhich == USAGE_ALL)
        doUsage("<width> <height> <bpp> <screen>", g_pszProgName, "setvideomode");
#endif
#ifdef VBOX_WITH_GUEST_PROPS
    if (eWhich == GUEST_PROP || eWhich == USAGE_ALL)
    {
        doUsage("get <property> [--verbose]", g_pszProgName, "guestproperty");
        doUsage("set <property> [<value> [--flags <flags>]]", g_pszProgName, "guestproperty");
        doUsage("delete|unset <property>", g_pszProgName, "guestproperty");
        doUsage("enumerate [--patterns <patterns>]", g_pszProgName, "guestproperty");
        doUsage("wait <patterns>", g_pszProgName, "guestproperty");
        doUsage("[--timestamp <last timestamp>]");
        doUsage("[--timeout <timeout in ms>");
    }
#endif
#ifdef VBOX_WITH_SHARED_FOLDERS
    if (eWhich  == GUEST_SHAREDFOLDERS || eWhich == USAGE_ALL)
    {
        doUsage("list [-automount]", g_pszProgName, "sharedfolder");
    }
#endif

#if !defined(VBOX_CONTROL_TEST)
    if (eWhich == WRITE_CORE_DUMP || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "writecoredump");
#endif
    if (eWhich == TAKE_SNAPSHOT || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "takesnapshot");
    if (eWhich == SAVE_STATE || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "savestate");
    if (eWhich == SUSPEND   || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "suspend");
    if (eWhich == POWER_OFF  || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "poweroff");
    if (eWhich == HELP      || eWhich == USAGE_ALL)
        doUsage("[command]", g_pszProgName, "help");
    if (eWhich == VERSION   || eWhich == USAGE_ALL)
        doUsage("", g_pszProgName, "version");
}

/** @} */

/**
 * Displays an error message.
 *
 * @returns RTEXITCODE_FAILURE.
 * @param   pszFormat   The message text. No newline.
 * @param   ...         Format arguments.
 */
static RTEXITCODE VBoxControlError(const char *pszFormat, ...)
{
    va_list va;
    va_start(va, pszFormat);
    RTMsgErrorV(pszFormat, va);
    va_end(va);
    return RTEXITCODE_FAILURE;
}


/**
 * Displays an syntax error message.
 *
 * @returns RTEXITCODE_FAILURE.
 * @param   pszFormat   The message text. No newline.
 * @param   ...         Format arguments.
 */
static RTEXITCODE VBoxControlSyntaxError(const char *pszFormat, ...)
{
    va_list va;
    va_start(va, pszFormat);
    RTMsgErrorV(pszFormat, va);
    va_end(va);
    return RTEXITCODE_FAILURE;
}

#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)

LONG (WINAPI * gpfnChangeDisplaySettingsEx)(LPCTSTR lpszDeviceName, LPDEVMODE lpDevMode, HWND hwnd, DWORD dwflags, LPVOID lParam);

static unsigned nextAdjacentRectXP (RECTL *paRects, unsigned nRects, unsigned iRect)
{
    unsigned i;
    for (i = 0; i < nRects; i++)
    {
        if (paRects[iRect].right == paRects[i].left)
        {
            return i;
        }
    }
    return ~0;
}

static unsigned nextAdjacentRectXN (RECTL *paRects, unsigned nRects, unsigned iRect)
{
    unsigned i;
    for (i = 0; i < nRects; i++)
    {
        if (paRects[iRect].left == paRects[i].right)
        {
            return i;
        }
    }
    return ~0;
}

static unsigned nextAdjacentRectYP (RECTL *paRects, unsigned nRects, unsigned iRect)
{
    unsigned i;
    for (i = 0; i < nRects; i++)
    {
        if (paRects[iRect].bottom == paRects[i].top)
        {
            return i;
        }
    }
    return ~0;
}

unsigned nextAdjacentRectYN (RECTL *paRects, unsigned nRects, unsigned iRect)
{
    unsigned i;
    for (i = 0; i < nRects; i++)
    {
        if (paRects[iRect].top == paRects[i].bottom)
        {
            return i;
        }
    }
    return ~0;
}

void resizeRect(RECTL *paRects, unsigned nRects, unsigned iPrimary, unsigned iResized, int NewWidth, int NewHeight)
{
    RECTL *paNewRects = (RECTL *)alloca (sizeof (RECTL) * nRects);
    memcpy (paNewRects, paRects, sizeof (RECTL) * nRects);
    paNewRects[iResized].right += NewWidth - (paNewRects[iResized].right - paNewRects[iResized].left);
    paNewRects[iResized].bottom += NewHeight - (paNewRects[iResized].bottom - paNewRects[iResized].top);

    /* Verify all pairs of originally adjacent rectangles for all 4 directions.
     * If the pair has a "good" delta (that is the first rectangle intersects the second)
     * at a direction and the second rectangle is not primary one (which can not be moved),
     * move the second rectangle to make it adjacent to the first one.
     */

    /* X positive. */
    unsigned iRect;
    for (iRect = 0; iRect < nRects; iRect++)
    {
        /* Find the next adjacent original rect in x positive direction. */
        unsigned iNextRect = nextAdjacentRectXP (paRects, nRects, iRect);
        Log(("next %d -> %d\n", iRect, iNextRect));

        if (iNextRect == ~0 || iNextRect == iPrimary)
        {
            continue;
        }

        /* Check whether there is an X intersection between these adjacent rects in the new rectangles
         * and fix the intersection if delta is "good".
         */
        int delta = paNewRects[iRect].right - paNewRects[iNextRect].left;

        if (delta > 0)
        {
            Log(("XP intersection right %d left %d, diff %d\n",
                     paNewRects[iRect].right, paNewRects[iNextRect].left,
                     delta));

            paNewRects[iNextRect].left += delta;
            paNewRects[iNextRect].right += delta;
        }
    }

    /* X negative. */
    for (iRect = 0; iRect < nRects; iRect++)
    {
        /* Find the next adjacent original rect in x negative direction. */
        unsigned iNextRect = nextAdjacentRectXN (paRects, nRects, iRect);
        Log(("next %d -> %d\n", iRect, iNextRect));

        if (iNextRect == ~0 || iNextRect == iPrimary)
        {
            continue;
        }

        /* Check whether there is an X intersection between these adjacent rects in the new rectangles
         * and fix the intersection if delta is "good".
         */
        int delta = paNewRects[iRect].left - paNewRects[iNextRect].right;

        if (delta < 0)
        {
            Log(("XN intersection left %d right %d, diff %d\n",
                     paNewRects[iRect].left, paNewRects[iNextRect].right,
                     delta));

            paNewRects[iNextRect].left += delta;
            paNewRects[iNextRect].right += delta;
        }
    }

    /* Y positive (in the computer sense, top->down). */
    for (iRect = 0; iRect < nRects; iRect++)
    {
        /* Find the next adjacent original rect in y positive direction. */
        unsigned iNextRect = nextAdjacentRectYP (paRects, nRects, iRect);
        Log(("next %d -> %d\n", iRect, iNextRect));

        if (iNextRect == ~0 || iNextRect == iPrimary)
        {
            continue;
        }

        /* Check whether there is an Y intersection between these adjacent rects in the new rectangles
         * and fix the intersection if delta is "good".
         */
        int delta = paNewRects[iRect].bottom - paNewRects[iNextRect].top;

        if (delta > 0)
        {
            Log(("YP intersection bottom %d top %d, diff %d\n",
                     paNewRects[iRect].bottom, paNewRects[iNextRect].top,
                     delta));

            paNewRects[iNextRect].top += delta;
            paNewRects[iNextRect].bottom += delta;
        }
    }

    /* Y negative (in the computer sense, down->top). */
    for (iRect = 0; iRect < nRects; iRect++)
    {
        /* Find the next adjacent original rect in x negative direction. */
        unsigned iNextRect = nextAdjacentRectYN (paRects, nRects, iRect);
        Log(("next %d -> %d\n", iRect, iNextRect));

        if (iNextRect == ~0 || iNextRect == iPrimary)
        {
            continue;
        }

        /* Check whether there is an Y intersection between these adjacent rects in the new rectangles
         * and fix the intersection if delta is "good".
         */
        int delta = paNewRects[iRect].top - paNewRects[iNextRect].bottom;

        if (delta < 0)
        {
            Log(("YN intersection top %d bottom %d, diff %d\n",
                     paNewRects[iRect].top, paNewRects[iNextRect].bottom,
                     delta));

            paNewRects[iNextRect].top += delta;
            paNewRects[iNextRect].bottom += delta;
        }
    }

    memcpy (paRects, paNewRects, sizeof (RECTL) * nRects);
    return;
}

/* Returns TRUE to try again. */
static BOOL ResizeDisplayDevice(ULONG Id, DWORD Width, DWORD Height, DWORD BitsPerPixel)
{
    BOOL fModeReset = (Width == 0 && Height == 0 && BitsPerPixel == 0);

    DISPLAY_DEVICE DisplayDevice;

    ZeroMemory(&DisplayDevice, sizeof(DisplayDevice));
    DisplayDevice.cb = sizeof(DisplayDevice);

    /* Find out how many display devices the system has */
    DWORD NumDevices = 0;
    DWORD i = 0;
    while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0))
    {
        Log(("[%d] %s\n", i, DisplayDevice.DeviceName));

        if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
        {
            Log(("Found primary device. err %d\n", GetLastError ()));
            NumDevices++;
        }
        else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
        {

            Log(("Found secondary device. err %d\n", GetLastError ()));
            NumDevices++;
        }

        ZeroMemory(&DisplayDevice, sizeof(DisplayDevice));
        DisplayDevice.cb = sizeof(DisplayDevice);
        i++;
    }

    Log(("Found total %d devices. err %d\n", NumDevices, GetLastError ()));

    if (NumDevices == 0 || Id >= NumDevices)
    {
        Log(("Requested identifier %d is invalid. err %d\n", Id, GetLastError ()));
        return FALSE;
    }

    DISPLAY_DEVICE *paDisplayDevices = (DISPLAY_DEVICE *)alloca (sizeof (DISPLAY_DEVICE) * NumDevices);
    DEVMODE *paDeviceModes = (DEVMODE *)alloca (sizeof (DEVMODE) * NumDevices);
    RECTL *paRects = (RECTL *)alloca (sizeof (RECTL) * NumDevices);

    /* Fetch information about current devices and modes. */
    DWORD DevNum = 0;
    DWORD DevPrimaryNum = 0;

    ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
    DisplayDevice.cb = sizeof(DISPLAY_DEVICE);

    i = 0;
    while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0))
    {
        Log(("[%d(%d)] %s\n", i, DevNum, DisplayDevice.DeviceName));

        BOOL bFetchDevice = FALSE;

        if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
        {
            Log(("Found primary device. err %d\n", GetLastError ()));
            DevPrimaryNum = DevNum;
            bFetchDevice = TRUE;
        }
        else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
        {

            Log(("Found secondary device. err %d\n", GetLastError ()));
            bFetchDevice = TRUE;
        }

        if (bFetchDevice)
        {
            if (DevNum >= NumDevices)
            {
                Log(("%d >= %d\n", NumDevices, DevNum));
                return FALSE;
            }

            paDisplayDevices[DevNum] = DisplayDevice;

            ZeroMemory(&paDeviceModes[DevNum], sizeof(DEVMODE));
            paDeviceModes[DevNum].dmSize = sizeof(DEVMODE);
            if (!EnumDisplaySettings((LPSTR)DisplayDevice.DeviceName,
                 ENUM_REGISTRY_SETTINGS, &paDeviceModes[DevNum]))
            {
                Log(("EnumDisplaySettings err %d\n", GetLastError ()));
                return FALSE;
            }

            Log(("%dx%d at %d,%d\n",
                    paDeviceModes[DevNum].dmPelsWidth,
                    paDeviceModes[DevNum].dmPelsHeight,
                    paDeviceModes[DevNum].dmPosition.x,
                    paDeviceModes[DevNum].dmPosition.y));

            paRects[DevNum].left   = paDeviceModes[DevNum].dmPosition.x;
            paRects[DevNum].top    = paDeviceModes[DevNum].dmPosition.y;
            paRects[DevNum].right  = paDeviceModes[DevNum].dmPosition.x + paDeviceModes[DevNum].dmPelsWidth;
            paRects[DevNum].bottom = paDeviceModes[DevNum].dmPosition.y + paDeviceModes[DevNum].dmPelsHeight;
            DevNum++;
        }

        ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
        DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
        i++;
    }

    if (Width == 0)
    {
        Width = paRects[Id].right - paRects[Id].left;
    }

    if (Height == 0)
    {
        Height = paRects[Id].bottom - paRects[Id].top;
    }

    /* Check whether a mode reset or a change is requested. */
    if (   !fModeReset
        && paRects[Id].right - paRects[Id].left == Width
        && paRects[Id].bottom - paRects[Id].top == Height
        && paDeviceModes[Id].dmBitsPerPel == BitsPerPixel)
    {
        Log(("VBoxDisplayThread : already at desired resolution.\n"));
        return FALSE;
    }

    resizeRect(paRects, NumDevices, DevPrimaryNum, Id, Width, Height);
#ifdef Log
    for (i = 0; i < NumDevices; i++)
    {
        Log(("[%d]: %d,%d %dx%d\n",
                i, paRects[i].left, paRects[i].top,
                paRects[i].right - paRects[i].left,
                paRects[i].bottom - paRects[i].top));
    }
#endif /* Log */

    /* Without this, Windows will not ask the miniport for its
     * mode table but uses an internal cache instead.
     */
    DEVMODE tempDevMode;
    ZeroMemory (&tempDevMode, sizeof (tempDevMode));
    tempDevMode.dmSize = sizeof(DEVMODE);
    EnumDisplaySettings(NULL, 0xffffff, &tempDevMode);

    /* Assign the new rectangles to displays. */
    for (i = 0; i < NumDevices; i++)
    {
        paDeviceModes[i].dmPosition.x = paRects[i].left;
        paDeviceModes[i].dmPosition.y = paRects[i].top;
        paDeviceModes[i].dmPelsWidth  = paRects[i].right - paRects[i].left;
        paDeviceModes[i].dmPelsHeight = paRects[i].bottom - paRects[i].top;

        paDeviceModes[i].dmFields = DM_POSITION | DM_PELSHEIGHT | DM_PELSWIDTH;

        if (   i == Id
            && BitsPerPixel != 0)
        {
            paDeviceModes[i].dmFields |= DM_BITSPERPEL;
            paDeviceModes[i].dmBitsPerPel = BitsPerPixel;
        }
        Log(("calling pfnChangeDisplaySettingsEx %x\n", gpfnChangeDisplaySettingsEx));
        gpfnChangeDisplaySettingsEx((LPSTR)paDisplayDevices[i].DeviceName,
                 &paDeviceModes[i], NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL);
        Log(("ChangeDisplaySettings position err %d\n", GetLastError ()));
    }

    /* A second call to ChangeDisplaySettings updates the monitor. */
    LONG status = ChangeDisplaySettings(NULL, 0);
    Log(("ChangeDisplaySettings update status %d\n", status));
    if (status == DISP_CHANGE_SUCCESSFUL || status == DISP_CHANGE_BADMODE)
    {
        /* Successfully set new video mode or our driver can not set the requested mode. Stop trying. */
        return FALSE;
    }

    /* Retry the request. */
    return TRUE;
}

static RTEXITCODE handleSetVideoMode(int argc, char *argv[])
{
    if (argc != 3 && argc != 4)
    {
        usage(SET_VIDEO_MODE);
        return RTEXITCODE_FAILURE;
    }

    DWORD xres = atoi(argv[0]);
    DWORD yres = atoi(argv[1]);
    DWORD bpp  = atoi(argv[2]);
    DWORD scr  = 0;

    if (argc == 4)
    {
        scr = atoi(argv[3]);
    }

    HMODULE hUser = GetModuleHandle("user32.dll");

    if (hUser)
    {
        *(uintptr_t *)&gpfnChangeDisplaySettingsEx = (uintptr_t)GetProcAddress(hUser, "ChangeDisplaySettingsExA");
        Log(("VBoxService: pChangeDisplaySettingsEx = %p\n", gpfnChangeDisplaySettingsEx));

        if (gpfnChangeDisplaySettingsEx)
        {
            /* The screen index is 0 based in the ResizeDisplayDevice call. */
            scr = scr > 0? scr - 1: 0;

            /* Horizontal resolution must be a multiple of 8, round down. */
            xres &= ~0x7;

            RTPrintf("Setting resolution of display %d to %dx%dx%d ...", scr, xres, yres, bpp);
            ResizeDisplayDevice(scr, xres, yres, bpp);
            RTPrintf("done.\n");
        }
        else
            VBoxControlError("Error retrieving API for display change!");
    }
    else
        VBoxControlError("Error retrieving handle to user32.dll!");

    return RTEXITCODE_SUCCESS;
}

static int checkVBoxVideoKey(HKEY hkeyVideo)
{
    char szValue[128];
    DWORD len = sizeof(szValue);
    DWORD dwKeyType;
    LONG status = RegQueryValueExA(hkeyVideo, "Device Description", NULL, &dwKeyType,
                                   (LPBYTE)szValue, &len);

    if (status == ERROR_SUCCESS)
    {
        /* WDDM has additional chars after "Adapter" */
        static char sszDeviceDescription[] = "VirtualBox Graphics Adapter";
        if (_strnicmp(szValue, sszDeviceDescription, sizeof(sszDeviceDescription) - sizeof(char)) == 0)
        {
            return VINF_SUCCESS;
        }
    }

    return VERR_NOT_FOUND;
}

static HKEY getVideoKey(bool writable)
{
    HKEY hkeyDeviceMap = 0;
    LONG status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\VIDEO", 0, KEY_READ, &hkeyDeviceMap);
    if (status != ERROR_SUCCESS || !hkeyDeviceMap)
    {
        VBoxControlError("Error opening video device map registry key!\n");
        return 0;
    }

    HKEY hkeyVideo = 0;
    ULONG iDevice;
    DWORD dwKeyType;

    /*
     * Scan all '\Device\VideoX' REG_SZ keys to find VBox video driver entry.
     * 'ObjectNumberList' REG_BINARY is an array of 32 bit device indexes (X).
     */

    /* Get the 'ObjectNumberList' */
    ULONG numDevices = 0;
    DWORD adwObjectNumberList[256];
    DWORD len = sizeof(adwObjectNumberList);
    status = RegQueryValueExA(hkeyDeviceMap, "ObjectNumberList", NULL, &dwKeyType, (LPBYTE)&adwObjectNumberList[0], &len);

    if (   status == ERROR_SUCCESS
        && dwKeyType == REG_BINARY)
    {
        numDevices = len / sizeof(DWORD);
    }
    else
    {
       /* The list might not exists. Use 'MaxObjectNumber' REG_DWORD and build a list. */
       DWORD dwMaxObjectNumber = 0;
       len = sizeof(dwMaxObjectNumber);
       status = RegQueryValueExA(hkeyDeviceMap, "MaxObjectNumber", NULL, &dwKeyType, (LPBYTE)&dwMaxObjectNumber, &len);

       if (   status == ERROR_SUCCESS
           && dwKeyType == REG_DWORD)
       {
           /* 'MaxObjectNumber' is inclusive. */
           numDevices = RT_MIN(dwMaxObjectNumber + 1, RT_ELEMENTS(adwObjectNumberList));
           for (iDevice = 0; iDevice < numDevices; iDevice++)
           {
               adwObjectNumberList[iDevice] = iDevice;
           }
       }
    }

    if (numDevices == 0)
    {
        /* Always try '\Device\Video0' as the old code did. Enum can be used in this case in principle. */
        adwObjectNumberList[0] = 0;
        numDevices = 1;
    }

    /* Scan device entries */
    for (iDevice = 0; iDevice < numDevices; iDevice++)
    {
        char szValueName[64];
        RTStrPrintf(szValueName, sizeof(szValueName), "\\Device\\Video%u", adwObjectNumberList[iDevice]);

        char szVideoLocation[256];
        len = sizeof(szVideoLocation);
        status = RegQueryValueExA(hkeyDeviceMap, szValueName, NULL, &dwKeyType, (LPBYTE)&szVideoLocation[0], &len);

        /* This value starts with '\REGISTRY\Machine' */
        if (   status == ERROR_SUCCESS
            && dwKeyType == REG_SZ
            && _strnicmp(szVideoLocation, "\\REGISTRY\\Machine", 17) == 0)
        {
            status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, &szVideoLocation[18], 0,
                                   KEY_READ | (writable ? KEY_WRITE : 0), &hkeyVideo);
            if (status == ERROR_SUCCESS)
            {
                int rc = checkVBoxVideoKey(hkeyVideo);
                if (RT_SUCCESS(rc))
                {
                    /* Found, return hkeyVideo to the caller. */
                    break;
                }

                RegCloseKey(hkeyVideo);
                hkeyVideo = 0;
            }
        }
    }

    if (hkeyVideo == 0)
    {
        VBoxControlError("Error opening video registry key!\n");
    }

    RegCloseKey(hkeyDeviceMap);
    return hkeyVideo;
}

static RTEXITCODE handleGetVideoAcceleration(int argc, char *argv[])
{
    ULONG status;
    HKEY hkeyVideo = getVideoKey(false);

    if (hkeyVideo)
    {
        /* query the actual value */
        DWORD fAcceleration = 1;
        DWORD len = sizeof(fAcceleration);
        DWORD dwKeyType;
        status = RegQueryValueExA(hkeyVideo, "EnableVideoAccel", NULL, &dwKeyType, (LPBYTE)&fAcceleration, &len);
        if (status != ERROR_SUCCESS)
            RTPrintf("Video acceleration: default\n");
        else
            RTPrintf("Video acceleration: %s\n", fAcceleration ? "on" : "off");
        RegCloseKey(hkeyVideo);
    }
    return RTEXITCODE_SUCCESS;
}

static RTEXITCODE handleSetVideoAcceleration(int argc, char *argv[])
{
    ULONG status;
    HKEY hkeyVideo;

    /* must have exactly one argument: the new offset */
    if (   (argc != 1)
        || (   RTStrICmp(argv[0], "on")
            && RTStrICmp(argv[0], "off")))
    {
        usage(SET_VIDEO_ACCEL);
        return RTEXITCODE_FAILURE;
    }

    hkeyVideo = getVideoKey(true);

    if (hkeyVideo)
    {
        int fAccel = 0;
        if (RTStrICmp(argv[0], "on") == 0)
            fAccel = 1;
        /* set a new value */
        status = RegSetValueExA(hkeyVideo, "EnableVideoAccel", 0, REG_DWORD, (LPBYTE)&fAccel, sizeof(fAccel));
        if (status != ERROR_SUCCESS)
        {
            VBoxControlError("Error %d writing video acceleration status!\n", status);
        }
        RegCloseKey(hkeyVideo);
    }
    return RTEXITCODE_SUCCESS;
}

static RTEXITCODE videoFlagsGet(void)
{
    HKEY hkeyVideo = getVideoKey(false);

    if (hkeyVideo)
    {
        DWORD dwFlags = 0;
        DWORD len = sizeof(dwFlags);
        DWORD dwKeyType;
        ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &len);
        if (status != ERROR_SUCCESS)
            RTPrintf("Video flags: default\n");
        else
            RTPrintf("Video flags: 0x%08X\n", dwFlags);
        RegCloseKey(hkeyVideo);
        return RTEXITCODE_SUCCESS;
    }

    return RTEXITCODE_FAILURE;
}

static RTEXITCODE videoFlagsDelete(void)
{
    HKEY hkeyVideo = getVideoKey(true);

    if (hkeyVideo)
    {
        ULONG status = RegDeleteValueA(hkeyVideo, "VBoxVideoFlags");
        if (status != ERROR_SUCCESS)
            VBoxControlError("Error %d deleting video flags.\n", status);
        RegCloseKey(hkeyVideo);
        return RTEXITCODE_SUCCESS;
    }

    return RTEXITCODE_FAILURE;
}

static RTEXITCODE videoFlagsModify(bool fSet, int argc, char *argv[])
{
    if (argc != 1)
    {
        VBoxControlError("Mask required.\n");
        return RTEXITCODE_FAILURE;
    }

    uint32_t u32Mask = 0;
    int rc = RTStrToUInt32Full(argv[0], 16, &u32Mask);
    if (RT_FAILURE(rc))
    {
        VBoxControlError("Invalid video flags mask.\n");
        return RTEXITCODE_FAILURE;
    }

    RTEXITCODE exitCode = RTEXITCODE_SUCCESS;

    HKEY hkeyVideo = getVideoKey(true);
    if (hkeyVideo)
    {
        DWORD dwFlags = 0;
        DWORD len = sizeof(dwFlags);
        DWORD dwKeyType;
        ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &len);
        if (status != ERROR_SUCCESS)
        {
            dwFlags = 0;
        }

        dwFlags = fSet? (dwFlags | u32Mask):
                        (dwFlags & ~u32Mask);

        status = RegSetValueExA(hkeyVideo, "VBoxVideoFlags", 0, REG_DWORD, (LPBYTE)&dwFlags, sizeof(dwFlags));
        if (status != ERROR_SUCCESS)
        {
            VBoxControlError("Error %d writing video flags.\n", status);
            exitCode = RTEXITCODE_FAILURE;
        }

        RegCloseKey(hkeyVideo);
    }
    else
    {
        exitCode = RTEXITCODE_FAILURE;
    }

    return exitCode;
}

static RTEXITCODE handleVideoFlags(int argc, char *argv[])
{
    /* Must have a keyword and optional value (32 bit hex string). */
    if (argc != 1 && argc != 2)
    {
        VBoxControlError("Invalid number of arguments.\n");
        usage(VIDEO_FLAGS);
        return RTEXITCODE_FAILURE;
    }

    RTEXITCODE exitCode = RTEXITCODE_SUCCESS;

    if (RTStrICmp(argv[0], "get") == 0)
    {
        exitCode = videoFlagsGet();
    }
    else if (RTStrICmp(argv[0], "delete") == 0)
    {
        exitCode = videoFlagsDelete();
    }
    else if (RTStrICmp(argv[0], "set") == 0)
    {
        exitCode = videoFlagsModify(true, argc - 1, &argv[1]);
    }
    else if (RTStrICmp(argv[0], "clear") == 0)
    {
        exitCode = videoFlagsModify(false, argc - 1, &argv[1]);
    }
    else
    {
        VBoxControlError("Invalid command.\n");
        exitCode = RTEXITCODE_FAILURE;
    }

    if (exitCode != RTEXITCODE_SUCCESS)
    {
        usage(VIDEO_FLAGS);
    }

    return exitCode;
}

#define MAX_CUSTOM_MODES 128

/* the table of custom modes */
struct
{
    DWORD xres;
    DWORD yres;
    DWORD bpp;
} customModes[MAX_CUSTOM_MODES] = {0};

void getCustomModes(HKEY hkeyVideo)
{
    ULONG status;
    int curMode = 0;

    /* null out the table */
    RT_ZERO(customModes);

    do
    {
        char valueName[20];
        DWORD xres, yres, bpp = 0;
        DWORD dwType;
        DWORD dwLen = sizeof(DWORD);

        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", curMode);
        status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&xres, &dwLen);
        if (status != ERROR_SUCCESS)
            break;
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", curMode);
        status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&yres, &dwLen);
        if (status != ERROR_SUCCESS)
            break;
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", curMode);
        status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&bpp, &dwLen);
        if (status != ERROR_SUCCESS)
            break;

        /* check if the mode is OK */
        if (   (xres > (1 << 16))
            && (yres > (1 << 16))
            && (   (bpp != 16)
                || (bpp != 24)
                || (bpp != 32)))
            break;

        /* add mode to table */
        customModes[curMode].xres = xres;
        customModes[curMode].yres = yres;
        customModes[curMode].bpp  = bpp;

        ++curMode;

        if (curMode >= MAX_CUSTOM_MODES)
            break;
    } while(1);
}

void writeCustomModes(HKEY hkeyVideo)
{
    ULONG status;
    int tableIndex = 0;
    int modeIndex = 0;

    /* first remove all values */
    for (int i = 0; i < MAX_CUSTOM_MODES; i++)
    {
        char valueName[20];
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", i);
        RegDeleteValueA(hkeyVideo, valueName);
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", i);
        RegDeleteValueA(hkeyVideo, valueName);
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", i);
        RegDeleteValueA(hkeyVideo, valueName);
    }

    do
    {
        if (tableIndex >= MAX_CUSTOM_MODES)
            break;

        /* is the table entry present? */
        if (   (!customModes[tableIndex].xres)
            || (!customModes[tableIndex].yres)
            || (!customModes[tableIndex].bpp))
        {
            tableIndex++;
            continue;
        }

        RTPrintf("writing mode %d (%dx%dx%d)\n", modeIndex, customModes[tableIndex].xres, customModes[tableIndex].yres, customModes[tableIndex].bpp);
        char valueName[20];
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", modeIndex);
        status = RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].xres,
                                sizeof(customModes[tableIndex].xres));
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", modeIndex);
        RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].yres,
                       sizeof(customModes[tableIndex].yres));
        RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", modeIndex);
        RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].bpp,
                       sizeof(customModes[tableIndex].bpp));

        modeIndex++;
        tableIndex++;

    } while(1);

}

static RTEXITCODE handleListCustomModes(int argc, char *argv[])
{
    if (argc != 0)
    {
        usage(LIST_CUST_MODES);
        return RTEXITCODE_FAILURE;
    }

    HKEY hkeyVideo = getVideoKey(false);

    if (hkeyVideo)
    {
        getCustomModes(hkeyVideo);
        for (int i = 0; i < (sizeof(customModes) / sizeof(customModes[0])); i++)
        {
            if (   !customModes[i].xres
                || !customModes[i].yres
                || !customModes[i].bpp)
                continue;

            RTPrintf("Mode: %d x %d x %d\n",
                             customModes[i].xres, customModes[i].yres, customModes[i].bpp);
        }
        RegCloseKey(hkeyVideo);
    }
    return RTEXITCODE_SUCCESS;
}

static RTEXITCODE handleAddCustomMode(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(ADD_CUST_MODE);
        return RTEXITCODE_FAILURE;
    }

    DWORD xres = atoi(argv[0]);
    DWORD yres = atoi(argv[1]);
    DWORD bpp  = atoi(argv[2]);

    /** @todo better check including xres mod 8 = 0! */
    if (   (xres > (1 << 16))
        && (yres > (1 << 16))
        && (   (bpp != 16)
            || (bpp != 24)
            || (bpp != 32)))
    {
        VBoxControlError("invalid mode specified!\n");
        return RTEXITCODE_FAILURE;
    }

    HKEY hkeyVideo = getVideoKey(true);

    if (hkeyVideo)
    {
        int i;
        int fModeExists = 0;
        getCustomModes(hkeyVideo);
        for (i = 0; i < MAX_CUSTOM_MODES; i++)
        {
            /* mode exists? */
            if (   customModes[i].xres == xres
                && customModes[i].yres == yres
                && customModes[i].bpp  == bpp
               )
            {
                fModeExists = 1;
            }
        }
        if (!fModeExists)
        {
            for (i = 0; i < MAX_CUSTOM_MODES; i++)
            {
                /* item free? */
                if (!customModes[i].xres)
                {
                    customModes[i].xres = xres;
                    customModes[i].yres = yres;
                    customModes[i].bpp  = bpp;
                    break;
                }
            }
            writeCustomModes(hkeyVideo);
        }
        RegCloseKey(hkeyVideo);
    }
    return RTEXITCODE_SUCCESS;
}

static RTEXITCODE handleRemoveCustomMode(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(REMOVE_CUST_MODE);
        return RTEXITCODE_FAILURE;
    }

    DWORD xres = atoi(argv[0]);
    DWORD yres = atoi(argv[1]);
    DWORD bpp  = atoi(argv[2]);

    HKEY hkeyVideo = getVideoKey(true);

    if (hkeyVideo)
    {
        getCustomModes(hkeyVideo);
        for (int i = 0; i < MAX_CUSTOM_MODES; i++)
        {
            /* correct item? */
            if (   (customModes[i].xres == xres)
                && (customModes[i].yres == yres)
                && (customModes[i].bpp  == bpp))
            {
                RTPrintf("found mode at index %d\n", i);
                RT_ZERO(customModes[i]);
                break;
            }
        }
        writeCustomModes(hkeyVideo);
        RegCloseKey(hkeyVideo);
    }

    return RTEXITCODE_SUCCESS;
}

#endif /* RT_OS_WINDOWS */

#ifdef VBOX_WITH_GUEST_PROPS
/**
 * Retrieves a value from the guest property store.
 * This is accessed through the "VBoxGuestPropSvc" HGCM service.
 *
 * @returns Command exit code.
 * @note see the command line API description for parameters
 */
static RTEXITCODE getGuestProperty(int argc, char **argv)
{
    using namespace guestProp;

    bool fVerbose = false;
    if (   2 == argc
        && (   strcmp(argv[1], "-verbose")  == 0
            || strcmp(argv[1], "--verbose") == 0)
       )
        fVerbose = true;
    else if (argc != 1)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }

    uint32_t u32ClientId = 0;
    int rc = VINF_SUCCESS;

    rc = VbglR3GuestPropConnect(&u32ClientId);
    if (!RT_SUCCESS(rc))
        VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);

    /*
     * Here we actually retrieve the value from the host.
     */
    const char *pszName = argv[0];
    char *pszValue = NULL;
    uint64_t u64Timestamp = 0;
    char *pszFlags = NULL;
    /* The buffer for storing the data and its initial size.  We leave a bit
     * of space here in case the maximum values are raised. */
    void *pvBuf = NULL;
    uint32_t cbBuf = MAX_VALUE_LEN + MAX_FLAGS_LEN + 1024;
    if (RT_SUCCESS(rc))
    {
        /* Because there is a race condition between our reading the size of a
         * property and the guest updating it, we loop a few times here and
         * hope.  Actually this should never go wrong, as we are generous
         * enough with buffer space. */
        bool finish = false;
        for (unsigned i = 0; (i < 10) && !finish; ++i)
        {
            void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
            if (NULL == pvTmpBuf)
            {
                rc = VERR_NO_MEMORY;
                VBoxControlError("Out of memory\n");
            }
            else
            {
                pvBuf = pvTmpBuf;
                rc = VbglR3GuestPropRead(u32ClientId, pszName, pvBuf, cbBuf,
                                         &pszValue, &u64Timestamp, &pszFlags,
                                         &cbBuf);
            }
            if (VERR_BUFFER_OVERFLOW == rc)
                /* Leave a bit of extra space to be safe */
                cbBuf += 1024;
            else
                finish = true;
        }
        if (VERR_TOO_MUCH_DATA == rc)
            VBoxControlError("Temporarily unable to retrieve the property\n");
        else if (!RT_SUCCESS(rc) && (rc != VERR_NOT_FOUND))
            VBoxControlError("Failed to retrieve the property value, error %Rrc\n", rc);
    }

    /*
     * And display it on the guest console.
     */
    if (VERR_NOT_FOUND == rc)
        RTPrintf("No value set!\n");
    else if (RT_SUCCESS(rc))
    {
        RTPrintf("Value: %s\n", pszValue);
        if (fVerbose)
        {
            RTPrintf("Timestamp: %lld ns\n", u64Timestamp);
            RTPrintf("Flags: %s\n", pszFlags);
        }
    }

    if (u32ClientId != 0)
        VbglR3GuestPropDisconnect(u32ClientId);
    RTMemFree(pvBuf);
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}


/**
 * Writes a value to the guest property store.
 * This is accessed through the "VBoxGuestPropSvc" HGCM service.
 *
 * @returns Command exit code.
 * @note see the command line API description for parameters
 */
static RTEXITCODE setGuestProperty(int argc, char *argv[])
{
    /*
     * Check the syntax.  We can deduce the correct syntax from the number of
     * arguments.
     */
    bool usageOK = true;
    const char *pszName = NULL;
    const char *pszValue = NULL;
    const char *pszFlags = NULL;
    if (2 == argc)
    {
        pszValue = argv[1];
    }
    else if (3 == argc)
        usageOK = false;
    else if (4 == argc)
    {
        pszValue = argv[1];
        if (   strcmp(argv[2], "-flags") != 0
            && strcmp(argv[2], "--flags") != 0)
            usageOK = false;
        pszFlags = argv[3];
    }
    else if (argc != 1)
        usageOK = false;
    if (!usageOK)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }
    /* This is always needed. */
    pszName = argv[0];

    /*
     * Do the actual setting.
     */
    uint32_t u32ClientId = 0;
    int rc = VINF_SUCCESS;
    rc = VbglR3GuestPropConnect(&u32ClientId);
    if (!RT_SUCCESS(rc))
        VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
    if (RT_SUCCESS(rc))
    {
        if (pszFlags != NULL)
            rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, pszFlags);
        else
            rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue);
        if (!RT_SUCCESS(rc))
            VBoxControlError("Failed to store the property value, error %Rrc\n", rc);
    }

    if (u32ClientId != 0)
        VbglR3GuestPropDisconnect(u32ClientId);
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}


/**
 * Deletes a guest property from the guest property store.
 * This is accessed through the "VBoxGuestPropSvc" HGCM service.
 *
 * @returns Command exit code.
 * @note see the command line API description for parameters
 */
static RTEXITCODE deleteGuestProperty(int argc, char *argv[])
{
    /*
     * Check the syntax.  We can deduce the correct syntax from the number of
     * arguments.
     */
    bool usageOK = true;
    const char *pszName = NULL;
    if (argc < 1)
        usageOK = false;
    if (!usageOK)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }
    /* This is always needed. */
    pszName = argv[0];

    /*
     * Do the actual setting.
     */
    uint32_t u32ClientId = 0;
    int rc = VbglR3GuestPropConnect(&u32ClientId);
    if (!RT_SUCCESS(rc))
        VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
    if (RT_SUCCESS(rc))
    {
        rc = VbglR3GuestPropDelete(u32ClientId, pszName);
        if (!RT_SUCCESS(rc))
            VBoxControlError("Failed to delete the property value, error %Rrc\n", rc);
    }

    if (u32ClientId != 0)
        VbglR3GuestPropDisconnect(u32ClientId);
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}


/**
 * Enumerates the properties in the guest property store.
 * This is accessed through the "VBoxGuestPropSvc" HGCM service.
 *
 * @returns Command exit code.
 * @note see the command line API description for parameters
 */
static RTEXITCODE enumGuestProperty(int argc, char *argv[])
{
    /*
     * Check the syntax.  We can deduce the correct syntax from the number of
     * arguments.
     */
    char const * const *papszPatterns = NULL;
    uint32_t cPatterns = 0;
    if (    argc > 1
        && (   strcmp(argv[0], "-patterns") == 0
            || strcmp(argv[0], "--patterns") == 0))
    {
        papszPatterns = (char const * const *)&argv[1];
        cPatterns = argc - 1;
    }
    else if (argc != 0)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }

    /*
     * Do the actual enumeration.
     */
    uint32_t u32ClientId = 0;
    int rc = VbglR3GuestPropConnect(&u32ClientId);
    if (RT_SUCCESS(rc))
    {
        PVBGLR3GUESTPROPENUM pHandle;
        const char *pszName, *pszValue, *pszFlags;
        uint64_t u64Timestamp;

        rc = VbglR3GuestPropEnum(u32ClientId, papszPatterns, cPatterns, &pHandle,
                                 &pszName, &pszValue, &u64Timestamp, &pszFlags);
        if (RT_SUCCESS(rc))
        {
            while (RT_SUCCESS(rc) && pszName)
            {
                RTPrintf("Name: %s, value: %s, timestamp: %lld, flags: %s\n",
                         pszName, pszValue, u64Timestamp, pszFlags);

                rc = VbglR3GuestPropEnumNext(pHandle, &pszName, &pszValue, &u64Timestamp, &pszFlags);
                if (RT_FAILURE(rc))
                    VBoxControlError("Error while enumerating guest properties: %Rrc\n", rc);
            }

            VbglR3GuestPropEnumFree(pHandle);
        }
        else if (VERR_NOT_FOUND == rc)
            RTPrintf("No properties found.\n");
        else
            VBoxControlError("Failed to enumerate the guest properties! Error: %Rrc\n", rc);
        VbglR3GuestPropDisconnect(u32ClientId);
    }
    else
        VBoxControlError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}


/**
 * Waits for notifications of changes to guest properties.
 * This is accessed through the "VBoxGuestPropSvc" HGCM service.
 *
 * @returns Command exit code.
 * @note see the command line API description for parameters
 */
static RTEXITCODE waitGuestProperty(int argc, char **argv)
{
    using namespace guestProp;

    /*
     * Handle arguments
     */
    const char *pszPatterns = NULL;
    uint64_t u64TimestampIn = 0;
    uint32_t u32Timeout = RT_INDEFINITE_WAIT;
    bool usageOK = true;
    if (argc < 1)
        usageOK = false;
    pszPatterns = argv[0];
    for (int i = 1; usageOK && i < argc; ++i)
    {
        if (   strcmp(argv[i], "-timeout")  == 0
            || strcmp(argv[i], "--timeout") == 0)
        {
            if (   i + 1 >= argc
                || RTStrToUInt32Full(argv[i + 1], 10, &u32Timeout)
                       != VINF_SUCCESS
               )
                usageOK = false;
            else
                ++i;
        }
        else if (   strcmp(argv[i], "-timestamp")  == 0
                 || strcmp(argv[i], "--timestamp") == 0)
        {
            if (   i + 1 >= argc
                || RTStrToUInt64Full(argv[i + 1], 10, &u64TimestampIn)
                       != VINF_SUCCESS
               )
                usageOK = false;
            else
                ++i;
        }
        else
            usageOK = false;
    }
    if (!usageOK)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }

    /*
     * Connect to the service
     */
    uint32_t u32ClientId = 0;
    int rc = VINF_SUCCESS;

    rc = VbglR3GuestPropConnect(&u32ClientId);
    if (!RT_SUCCESS(rc))
        VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);

    /*
     * Retrieve the notification from the host
     */
    char *pszName = NULL;
    char *pszValue = NULL;
    uint64_t u64TimestampOut = 0;
    char *pszFlags = NULL;
    /* The buffer for storing the data and its initial size.  We leave a bit
     * of space here in case the maximum values are raised. */
    void *pvBuf = NULL;
    uint32_t cbBuf = MAX_NAME_LEN + MAX_VALUE_LEN + MAX_FLAGS_LEN + 1024;
    /* Because there is a race condition between our reading the size of a
     * property and the guest updating it, we loop a few times here and
     * hope.  Actually this should never go wrong, as we are generous
     * enough with buffer space. */
    bool finish = false;
    for (unsigned i = 0;
         (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) && !finish && (i < 10);
         ++i)
    {
        void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
        if (NULL == pvTmpBuf)
        {
            rc = VERR_NO_MEMORY;
            VBoxControlError("Out of memory\n");
        }
        else
        {
            pvBuf = pvTmpBuf;
            rc = VbglR3GuestPropWait(u32ClientId, pszPatterns, pvBuf, cbBuf,
                                     u64TimestampIn, u32Timeout,
                                     &pszName, &pszValue, &u64TimestampOut,
                                     &pszFlags, &cbBuf);
        }
        if (VERR_BUFFER_OVERFLOW == rc)
            /* Leave a bit of extra space to be safe */
            cbBuf += 1024;
        else
            finish = true;
        if (rc == VERR_TOO_MUCH_DATA)
            VBoxControlError("Temporarily unable to get a notification\n");
        else if (rc == VERR_INTERRUPTED)
            VBoxControlError("The request timed out or was interrupted\n");
#ifndef RT_OS_WINDOWS  /* Windows guests do not do this right */
        else if (!RT_SUCCESS(rc) && (rc != VERR_NOT_FOUND))
            VBoxControlError("Failed to get a notification, error %Rrc\n", rc);
#endif
    }

    /*
     * And display it on the guest console.
     */
    if (VERR_NOT_FOUND == rc)
        RTPrintf("No value set!\n");
    else if (rc == VERR_BUFFER_OVERFLOW)
        RTPrintf("Internal error: unable to determine the size of the data!\n");
    else if (RT_SUCCESS(rc))
    {
        RTPrintf("Name: %s\n", pszName);
        RTPrintf("Value: %s\n", pszValue);
        RTPrintf("Timestamp: %lld ns\n", u64TimestampOut);
        RTPrintf("Flags: %s\n", pszFlags);
    }

    if (u32ClientId != 0)
        VbglR3GuestPropDisconnect(u32ClientId);
    RTMemFree(pvBuf);
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}


/**
 * Access the guest property store through the "VBoxGuestPropSvc" HGCM
 * service.
 *
 * @returns 0 on success, 1 on failure
 * @note see the command line API description for parameters
 */
static RTEXITCODE handleGuestProperty(int argc, char *argv[])
{
    if (0 == argc)
    {
        usage(GUEST_PROP);
        return RTEXITCODE_FAILURE;
    }
    if (!strcmp(argv[0], "get"))
        return getGuestProperty(argc - 1, argv + 1);
    else if (!strcmp(argv[0], "set"))
        return setGuestProperty(argc - 1, argv + 1);
    else if (!strcmp(argv[0], "delete") || !strcmp(argv[0], "unset"))
        return deleteGuestProperty(argc - 1, argv + 1);
    else if (!strcmp(argv[0], "enumerate"))
        return enumGuestProperty(argc - 1, argv + 1);
    else if (!strcmp(argv[0], "wait"))
        return waitGuestProperty(argc - 1, argv + 1);
    /* else */
    usage(GUEST_PROP);
    return RTEXITCODE_FAILURE;
}
#endif

#ifdef VBOX_WITH_SHARED_FOLDERS
/**
 * Lists the Shared Folders provided by the host.
 */
static RTEXITCODE listSharedFolders(int argc, char **argv)
{
    bool usageOK = true;
    bool fOnlyShowAutoMount = false;
    if (argc == 1)
    {
        if (   !strcmp(argv[0], "-automount")
            || !strcmp(argv[0], "--automount"))
            fOnlyShowAutoMount = true;
        else
            usageOK = false;
    }
    else if (argc > 1)
        usageOK = false;

    if (!usageOK)
    {
        usage(GUEST_SHAREDFOLDERS);
        return RTEXITCODE_FAILURE;
    }

    uint32_t u32ClientId;
    int rc = VbglR3SharedFolderConnect(&u32ClientId);
    if (!RT_SUCCESS(rc))
        VBoxControlError("Failed to connect to the shared folder service, error %Rrc\n", rc);
    else
    {
        PVBGLR3SHAREDFOLDERMAPPING paMappings;
        uint32_t cMappings;
        rc = VbglR3SharedFolderGetMappings(u32ClientId, fOnlyShowAutoMount,
                                           &paMappings, &cMappings);
        if (RT_SUCCESS(rc))
        {
            if (fOnlyShowAutoMount)
                RTPrintf("Auto-mounted Shared Folder mappings (%u):\n\n", cMappings);
            else
                RTPrintf("Shared Folder mappings (%u):\n\n", cMappings);

            for (uint32_t i = 0; i < cMappings; i++)
            {
                char *pszName;
                rc = VbglR3SharedFolderGetName(u32ClientId, paMappings[i].u32Root, &pszName);
                if (RT_SUCCESS(rc))
                {
                    RTPrintf("%02u - %s\n", i + 1, pszName);
                    RTStrFree(pszName);
                }
                else
                    VBoxControlError("Error while getting the shared folder name for root node = %u, rc = %Rrc\n",
                                     paMappings[i].u32Root, rc);
            }
            if (!cMappings)
                RTPrintf("No Shared Folders available.\n");
            VbglR3SharedFolderFreeMappings(paMappings);
        }
        else
            VBoxControlError("Error while getting the shared folder mappings, rc = %Rrc\n", rc);
        VbglR3SharedFolderDisconnect(u32ClientId);
    }
    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}

/**
 * Handles Shared Folders control.
 *
 * @returns 0 on success, 1 on failure
 * @note see the command line API description for parameters
 *      (r=bird: yeah, right. The API description contains nil about params)
 */
static RTEXITCODE handleSharedFolder(int argc, char *argv[])
{
    if (0 == argc)
    {
        usage(GUEST_SHAREDFOLDERS);
        return RTEXITCODE_FAILURE;
    }
    if (!strcmp(argv[0], "list"))
        return listSharedFolders(argc - 1, argv + 1);
    /* else */
    usage(GUEST_SHAREDFOLDERS);
    return RTEXITCODE_FAILURE;
}
#endif

#if !defined(VBOX_CONTROL_TEST)
/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writecoredump}
 */
static RTEXITCODE handleWriteCoreDump(int argc, char *argv[])
{
    int rc = VbglR3WriteCoreDump();
    if (RT_SUCCESS(rc))
    {
        RTPrintf("Guest core dump successful.\n");
        return RTEXITCODE_SUCCESS;
    }
    else
    {
        VBoxControlError("Error while taking guest core dump. rc=%Rrc\n", rc);
        return RTEXITCODE_FAILURE;
    }
}
#endif

#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help}
 */
static RTEXITCODE handleDpc(int argc, char *argv[])
{
# ifndef VBOX_CONTROL_TEST
    int rc;
    for (int i = 0; i < 30; i++)
    {
        rc = vbglR3DoIOCtl(VBOXGUEST_IOCTL_DPC_LATENCY_CHECKER, NULL, 0);
        if (RT_FAILURE(rc))
            break;
        RTPrintf("%d\n", i);
    }
# else
    int rc = VERR_NOT_IMPLEMENTED;
# endif
    if (RT_FAILURE(rc))
        return VBoxControlError("Error. rc=%Rrc\n", rc);
    RTPrintf("Samples collection completed.\n");
    return RTEXITCODE_SUCCESS;
}
#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */


/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: takesnapshot}
 */
static RTEXITCODE handleTakeSnapshot(int argc, char *argv[])
{
    //VbglR3VmTakeSnapshot(argv[0], argv[1]);
    return VBoxControlError("not implemented");
}

/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: savestate}
 */
static RTEXITCODE handleSaveState(int argc, char *argv[])
{
    //VbglR3VmSaveState();
    return VBoxControlError("not implemented");
}

/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: suspend|pause}
 */
static RTEXITCODE handleSuspend(int argc, char *argv[])
{
    //VbglR3VmSuspend();
    return VBoxControlError("not implemented");
}

/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: poweroff|powerdown}
 */
static RTEXITCODE handlePowerOff(int argc, char *argv[])
{
    //VbglR3VmPowerOff();
    return VBoxControlError("not implemented");
}

/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: version}
 */
static RTEXITCODE handleVersion(int argc, char *argv[])
{
    if (argc)
        return VBoxControlSyntaxError("getversion does not take any arguments");
    RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision());
    return RTEXITCODE_SUCCESS;
}

/**
 * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help}
 */
static RTEXITCODE handleHelp(int argc, char *argv[])
{
    /* ignore arguments for now. */
    usage();
    return RTEXITCODE_SUCCESS;
}


/** command handler type */
typedef DECLCALLBACK(RTEXITCODE) FNVBOXCTRLCMDHANDLER(int argc, char *argv[]);
typedef FNVBOXCTRLCMDHANDLER *PFNVBOXCTRLCMDHANDLER;

/** The table of all registered command handlers. */
struct COMMANDHANDLER
{
    const char *pszCommand;
    PFNVBOXCTRLCMDHANDLER pfnHandler;
} g_aCommandHandlers[] =
{
#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)
    { "getvideoacceleration",   handleGetVideoAcceleration },
    { "setvideoacceleration",   handleSetVideoAcceleration },
    { "videoflags",             handleVideoFlags },
    { "listcustommodes",        handleListCustomModes },
    { "addcustommode",          handleAddCustomMode },
    { "removecustommode",       handleRemoveCustomMode },
    { "setvideomode",           handleSetVideoMode },
#endif
#ifdef VBOX_WITH_GUEST_PROPS
    { "guestproperty",          handleGuestProperty },
#endif
#ifdef VBOX_WITH_SHARED_FOLDERS
    { "sharedfolder",           handleSharedFolder },
#endif
#if !defined(VBOX_CONTROL_TEST)
    { "writecoredump",          handleWriteCoreDump },
#endif
#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
    { "dpc",                    handleDpc },
#endif
    { "takesnapshot",           handleTakeSnapshot },
    { "savestate",              handleSaveState },
    { "suspend",                handleSuspend },
    { "pause",                  handleSuspend },
    { "poweroff",               handlePowerOff },
    { "powerdown",              handlePowerOff },
    { "getversion",             handleVersion },
    { "version",                handleVersion },
    { "help",                   handleHelp }
};

/** Main function */
int main(int argc, char **argv)
{
    /** The application's global return code */
    RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
    /** An IPRT return code for local use */
    int rrc = VINF_SUCCESS;
    /** The index of the command line argument we are currently processing */
    int iArg = 1;
    /** Should we show the logo text? */
    bool fShowLogo = true;
    /** Should we print the usage after the logo?  For the -help switch. */
    bool fDoHelp = false;
    /** Will we be executing a command or just printing information? */
    bool fOnlyInfo = false;

    rrc = RTR3InitExe(argc, &argv, 0);
    if (RT_FAILURE(rrc))
        return RTMsgInitFailure(rrc);

    /*
     * Start by handling command line switches
     */
    /** @todo RTGetOpt conversion of the whole file. */
    bool done = false;      /**< Are we finished with handling switches? */
    while (!done && (iArg < argc))
    {
        if (   !strcmp(argv[iArg], "-V")
            || !strcmp(argv[iArg], "-v")
            || !strcmp(argv[iArg], "--version")
            || !strcmp(argv[iArg], "-version")
           )
        {
            /* Print version number, and do nothing else. */
            RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision());
            fOnlyInfo = true;
            fShowLogo = false;
            done = true;
        }
        else if (   !strcmp(argv[iArg], "-nologo")
                 || !strcmp(argv[iArg], "--nologo"))
            fShowLogo = false;
        else if (   !strcmp(argv[iArg], "-help")
                 || !strcmp(argv[iArg], "--help"))
        {
            fOnlyInfo = true;
            fDoHelp = true;
            done = true;
        }
        else
            /* We have found an argument which isn't a switch.  Exit to the
             * command processing bit. */
            done = true;
        if (!done)
            ++iArg;
    }

    /*
     * Find the application name, show our logo if the user hasn't suppressed it,
     * and show the usage if the user asked us to
     */
    g_pszProgName = RTPathFilename(argv[0]);
    if (fShowLogo)
        RTPrintf(VBOX_PRODUCT " Guest Additions Command Line Management Interface Version "
                 VBOX_VERSION_STRING "\n"
                 "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
                 "All rights reserved.\n\n");
    if (fDoHelp)
        usage();

    /*
     * Do global initialisation for the programme if we will be handling a command
     */
    if (!fOnlyInfo)
    {
        rrc = VbglR3Init();
        if (RT_FAILURE(rrc))
        {
            VBoxControlError("Could not contact the host system.  Make sure that you are running this\n"
                             "application inside a VirtualBox guest system, and that you have sufficient\n"
                             "user permissions.\n");
            rcExit = RTEXITCODE_FAILURE;
        }
    }

    /*
     * Now look for an actual command in the argument list and handle it.
     */

    if (!fOnlyInfo && rcExit == RTEXITCODE_SUCCESS)
    {
        if (argc > iArg)
        {
            /*
             * Try locate the command and execute it, complain if not found.
             */
            unsigned i;
            for (i = 0; i < RT_ELEMENTS(g_aCommandHandlers); i++)
                if (!strcmp(argv[iArg], g_aCommandHandlers[i].pszCommand))
                {
                    rcExit = g_aCommandHandlers[i].pfnHandler(argc - iArg - 1, argv + iArg + 1);
                    break;
                }
            if (i >= RT_ELEMENTS(g_aCommandHandlers))
            {
                rcExit = RTEXITCODE_FAILURE;
                usage();
            }
        }
        else
        {
            /* The user didn't specify a command. */
            rcExit = RTEXITCODE_FAILURE;
            usage();
        }
    }

    /*
     * And exit, returning the status
     */
    return rcExit;
}

