/* $Id: VBoxVMMPreload.cpp $ */
/** @file
 * VBoxVMMPreload - Preload VBox the ring-0 modules.
 */

/*
 * Copyright (C) 2012 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/buildconfig.h>
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/message.h>
#include <iprt/path.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/thread.h>

#include <VBox/sup.h>
#include <VBox/version.h>


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/**
 * Known modules and their associated data (there are only known modules!).
 */
static struct
{
    const char     *pszName;
    bool            fPreload;
    void           *pvImageBase;
} g_aModules[] =
{
    { "VMMR0.r0",       true,  NULL },
    { "VBoxDDR0.r0",    true,  NULL },
    { "VBoxDD2R0.r0",   true,  NULL },
};

static uint32_t     g_cVerbose = 1;


/**
 * Parses the options.
 *
 * @returns RTEXITCODE_SUCCESS on success.
 * @param   argc                Argument count .
 * @param   argv                Argument vector.
 * @param   pfExit              Set to @c true if we should exit.
 */
static RTEXITCODE ParseOptions(int argc, char **argv, bool *pfExit)
{
    /*
     * Parse arguments.
     */
    static const RTGETOPTDEF s_aOptions[] =
    {
        { "--only",     'o', RTGETOPT_REQ_STRING  },
        { "--quiet",    'q', RTGETOPT_REQ_NOTHING },
        { "--verbose",  'v', RTGETOPT_REQ_NOTHING },
    };

    bool            fAll     = true;

    int             ch;
    RTGETOPTUNION   ValueUnion;
    RTGETOPTSTATE   GetState;
    RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
    while ((ch = RTGetOpt(&GetState, &ValueUnion)))
    {
        switch(ch)
        {
            case 'o':
            {
                uint32_t i;

                if (fAll)
                {
                    fAll = false;
                    for (i = 0; i < RT_ELEMENTS(g_aModules); i++)
                        g_aModules[i].fPreload = false;
                }

                i = RT_ELEMENTS(g_aModules);
                while (i-- > 0)
                    if (!strcmp(ValueUnion.psz, g_aModules[i].pszName))
                    {
                        g_aModules[i].fPreload = true;
                        break;
                    }
                if (i > RT_ELEMENTS(g_aModules))
                    return RTMsgErrorExit(RTEXITCODE_FAILURE, "No known module '%s'", ValueUnion.psz);
                break;
            }

            case 'v':
                g_cVerbose++;
                break;

            case 'q':
                g_cVerbose = 0;
                break;

            case 'h':
                RTPrintf(VBOX_PRODUCT " VMM ring-0 Module Preloader Version " VBOX_VERSION_STRING
                         "(C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
                         "All rights reserved.\n"
                         "\n"
                         "Usage: VBoxVMMPreload [-hqvV] [-o|--only <mod>]\n"
                         "\n");
                *pfExit = true;
                return RTEXITCODE_SUCCESS;

            case 'V':
                RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
                *pfExit = true;
                return RTEXITCODE_SUCCESS;

            default:
                return RTGetOptPrintError(ch, &ValueUnion);
        }
    }
    return RTEXITCODE_SUCCESS;
}


/**
 * Loads the modules.
 *
 * @returns RTEXITCODE_SUCCESS on success.
 */
static RTEXITCODE LoadModules(void)
{
    for (uint32_t i = 0; i < RT_ELEMENTS(g_aModules); i++)
    {
        if (g_aModules[i].fPreload)
        {
            char            szPath[RTPATH_MAX];
            int rc = RTPathAppPrivateArch(szPath, sizeof(szPath));
            if (RT_SUCCESS(rc))
                rc = RTPathAppend(szPath, sizeof(szPath), g_aModules[i].pszName);
            if (RT_FAILURE(rc))
                return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppPrivateArch or RTPathAppend returned %Rrc", rc);

            void           *pvImageBase;
            RTERRINFOSTATIC ErrInfo;
            RTErrInfoInitStatic(&ErrInfo);
            rc = SUPR3LoadModule(szPath, g_aModules[i].pszName, &g_aModules[i].pvImageBase, &ErrInfo.Core);
            if (RT_FAILURE(rc))
                return RTMsgErrorExit(RTEXITCODE_FAILURE, "SUPR3LoadModule failed for %s (%s): %s (rc=%Rrc)",
                                      g_aModules[i].pszName, szPath, ErrInfo.Core.pszMsg, rc);
            if (g_cVerbose >= 1)
                RTMsgInfo("Loaded '%s' ('%s') at %p\n", szPath, g_aModules[i].pszName, g_aModules[i].pvImageBase);
        }
    }

    RTStrmFlush(g_pStdOut);
    return RTEXITCODE_SUCCESS;
}


/**
 *  Entry point.
 */
extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
{
    bool fExit = false;
    RTEXITCODE rcExit = ParseOptions(argc, argv, &fExit);
    if (rcExit == RTEXITCODE_SUCCESS && !fExit)
    {
        rcExit = LoadModules();
        if (rcExit == RTEXITCODE_SUCCESS)
        {
            for (;;)
                RTThreadSleep(RT_INDEFINITE_WAIT);
        }
    }
    return rcExit;
}


#ifndef VBOX_WITH_HARDENING
/**
 * Main entry point.
 */
int main(int argc, char **argv, char **envp)
{
    int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
    if (RT_FAILURE(rc))
        return RTMsgInitFailure(rc);
    return TrustedMain(argc, argv, envp);
}
#endif /* !VBOX_WITH_HARDENING */

