/*++

Copyright (c) 1993-1996 Microsoft Corporation

Module Name:

    nvram.c

Abstract:

    ARC/NV-RAM manipulation routines for 32-bit winnt setup.
    Also works on boot.ini on i386 machines.

Author:

    Ted Miller (tedm) 19-December-1993

Revision History:

--*/


#include "nvram.h"

typedef enum {
    BootVarSystemPartition,
    BootVarOsLoader,
    BootVarOsLoadPartition,
    BootVarOsLoadFilename,
    BootVarLoadIdentifier,
    BootVarOsLoadOptions,
    BootVarCountdown,
    BootVarMax
} BOOT_VARS;

PWSTR BootVarNames[BootVarMax] = { L"SYSTEMPARTITION",
                                   L"OSLOADER",
                                   L"OSLOADPARTITION",
                                   L"OSLOADFILENAME",
                                   L"LOADIDENTIFIER",
                                   L"OSLOADOPTIONS",
                                   L"COUNTDOWN"
                                 };

PWSTR PaddedBootVarNames[BootVarMax] = { L"SYSTEMPARTITION",
                                         L"       OSLOADER",
                                         L"OSLOADPARTITION",
                                         L" OSLOADFILENAME",
                                         L" LOADIDENTIFIER",
                                         L"  OSLOADOPTIONS",
                                         L"      COUNTDOWN"
                                       };

#ifndef i386

//
// Helper macro to make object attribute initialization a little cleaner.
//
#define INIT_OBJA(Obja,UnicodeString,UnicodeText)           \
                                                            \
    RtlInitUnicodeString((UnicodeString),(UnicodeText));    \
                                                            \
    InitializeObjectAttributes(                             \
        (Obja),                                             \
        (UnicodeString),                                    \
        OBJ_CASE_INSENSITIVE,                               \
        NULL,                                               \
        NULL                                                \
        )



BOOL
DoSetNvRamVar(
    IN PWSTR VarName,
    IN PWSTR VarValue
    )
{
    UNICODE_STRING U1,U2;

    RtlInitUnicodeString(&U1,VarName);
    RtlInitUnicodeString(&U2,VarValue);

    return(NT_SUCCESS(NtSetSystemEnvironmentValue(&U1,&U2)));
}


VOID
PrintNvRamVariable(
    IN PWSTR VariableName,
    IN PWSTR VariableValue
    )
{
    PWSTR pEnd;
    WCHAR c;
    BOOL FirstComponent = TRUE;

    while(*VariableValue) {

        //
        // Find the termination of the current component,
        // which is either a ; or 0.
        //
        pEnd = wcschr(VariableValue,L';');
        if(!pEnd) {
            pEnd = wcschr(VariableValue,0);
        }

        c = *pEnd;
        *pEnd = 0;

        wprintf(
            L"%s%s %s\n",
            FirstComponent ? VariableName : L"               ",
            FirstComponent ? L":" : L" ",
            VariableValue
            );

        *pEnd = c;

        VariableValue = pEnd + (c ? 1 : 0);

        FirstComponent = FALSE;
    }
}

VOID
RotateNvRamVariable(
    IN PWSTR VariableValue
    )
{
    PWSTR pEnd;
    WCHAR Buffer[32768];
    //
    // Find the termination of the current component,
    // which is either a ; or 0.
    //
    pEnd = wcschr(VariableValue,L';');
    if(!pEnd) {
        pEnd = wcschr(VariableValue,0);
    }

    //
    // Copy VariableValue into Buffer starting at second entry
    //
    wcscpy(Buffer, pEnd + (*pEnd ? 1 : 0));

    //
    // Append first entry at the end of Buffer
    //
    if (*pEnd) wcscpy(Buffer + wcslen(Buffer), L";");

    *pEnd = 0;

    wcscpy(Buffer + wcslen(Buffer), VariableValue);

    //
    // Copy whole thing back into VariableValue
    //
    wcscpy(VariableValue, Buffer);

}

int _cdecl main(
    IN int argc,
    IN char *argv[]
    )
{
    DWORD var;
    UNICODE_STRING UnicodeString;
    NTSTATUS Status;
    BOOLEAN OldPriv;
    WCHAR Buffer[32768];
    WCHAR Buffer1[32768];
    WCHAR Buffer2[32768];

    Status = RtlAdjustPrivilege(
                SE_SYSTEM_ENVIRONMENT_PRIVILEGE,
                TRUE,
                FALSE,
                &OldPriv
                );

    if(!NT_SUCCESS(Status)) {
        wprintf(L"Insufficient privilege.\n");
        return(0);
    }

    if(argc == 1) {

        for(var=0; var<BootVarMax; var++) {

            RtlInitUnicodeString(&UnicodeString,BootVarNames[var]);

            Status = NtQuerySystemEnvironmentValue(
                        &UnicodeString,
                        Buffer,
                        SIZECHARS(Buffer),
                        NULL
                        );

            if(NT_SUCCESS(Status)) {
                PrintNvRamVariable(PaddedBootVarNames[var],Buffer);
            } else {
                wprintf(L"%s: <empty>\n",PaddedBootVarNames[var]);
            }

            wprintf(L"\n");
        }
    }

    if((argc == 2) && !lstrcmpiA(argv[1],"rotate")){

        for(var=0; var<BootVarMax; var++) {

            RtlInitUnicodeString(&UnicodeString,BootVarNames[var]);

            Status = NtQuerySystemEnvironmentValue(
                        &UnicodeString,
                        Buffer,
                        SIZECHARS(Buffer),
                        NULL
                        );

            if(NT_SUCCESS(Status)) {
                RotateNvRamVariable(Buffer);
                printf(
                    "Setting variable %ws to %ws [%s]\n",
                    UnicodeString.Buffer,
                    Buffer,
                    DoSetNvRamVar(UnicodeString.Buffer,Buffer) ? "OK" : "Error"
                );
            } else {
                wprintf(L"%s: <empty>\n",PaddedBootVarNames[var]);
            }

            wprintf(L"\n");
        }
    }

    if((argc == 5) && !lstrcmpiA(argv[1]+1,"set") && !lstrcmpA(argv[3],"=")) {

        MultiByteToWideChar(
            CP_OEMCP,
            MB_PRECOMPOSED,
            argv[2],
            -1,
            Buffer1,
            SIZECHARS(Buffer1)
            );

        MultiByteToWideChar(
            CP_OEMCP,
            MB_PRECOMPOSED,
            argv[4],
            -1,
            Buffer2,
            SIZECHARS(Buffer2)
            );

        printf(
            "Setting variable %ws to %ws [%s]\n",
            Buffer1,
            Buffer2,
            DoSetNvRamVar(Buffer1,Buffer2) ? "OK" : "Error"
            );
    }

    return(0);
}

#else

TCHAR LoadID[500];          // load identifier (no quotes)
TCHAR CountDown[100];       // countdown timer
TCHAR OsLoadOptions[500];   // load options
TCHAR OsName[500];          // name of default os

TCHAR OsLine[500];          // complete line of os description and options

#define STR_BOOTINI           TEXT("c:\\boot.ini")
#define STR_BOOTLDR           TEXT("boot loader")
#define STR_TIMEOUT           TEXT("timeout")
#define STR_DEFAULT           TEXT("default")
#define STR_OPERATINGSYS      TEXT("operating systems")
#define STR_NULL              TEXT("")

//
// HandleOption - add option to OsLoadOptions
//

VOID HandleOption( TCHAR* Option )
{
    TCHAR SlashOption[200];
    TCHAR SlashOptionSlash[200];
    //
    // find out if option already exists
    // add blank to end to prevent debug from matching debugport
    //

    wsprintf( SlashOption, TEXT("/%s "), Option );
    wsprintf( SlashOptionSlash, TEXT("/%s/"), Option );

    if( wcsstr( OsLoadOptions, SlashOption )   || 
        wcsstr( OsLoadOptions, SlashOptionSlash ) )
    {
        printf("option already exists: %ws\n",Option);
    }
    else
    {
        //
        // append option without the trailing blank
        //

        printf("added option %ws\n",Option);
        lstrcat( OsLoadOptions, TEXT("/") );
        lstrcat( OsLoadOptions, Option );
    }
}

//
// WriteBootIni - update the boot.ini file with our changes
//

VOID WriteBootIni()
{
    DWORD FileAttr;

    //
    // Get file attributes of boot.ini for later restoration
    //

    FileAttr= GetFileAttributes( STR_BOOTINI );

    //
    // Change file attributes on boot.ini so we can write to it.
    //

    if( !SetFileAttributes( STR_BOOTINI, FILE_ATTRIBUTE_NORMAL ) )
    {
        printf("Failed to turn off read-only on boot.ini  (lasterr= %d)\n",
                GetLastError() );
    }

    //
    // Update boot.ini strings
    //

    if( !WritePrivateProfileString( STR_BOOTLDR, STR_TIMEOUT, 
                                   CountDown, STR_BOOTINI ) )
    {
        printf("failed to write timeout (lasterr= %d)\n",GetLastError());
    }

    //
    // create the osline from its parts
    //
    
    wsprintf(OsLine, TEXT("\"%s\"%s"), LoadID, OsLoadOptions );

    if( !WritePrivateProfileString( STR_OPERATINGSYS, OsName,  
                                    OsLine, STR_BOOTINI ) )
    {
        printf("failed to write OS line (lasterr= %d)\n",GetLastError());
    }

    //
    // Restore boot.ini file attributes
    //

    if( FileAttr != 0xFFFFFFFF )
    {
        SetFileAttributes( STR_BOOTINI, FileAttr );
    }

}

//
// Usage - print out usage information
//

VOID Usage()
{
        printf("\nUsage:\n");
        printf("    no parameters:  prints current settings.\n");
        printf("   /set parameter = value  : sets value in boot.ini\n");
        printf("   rotate : rotates default build through boot options\n");
        printf("\n");
        printf("Example:  nvram /set osloadoptions = debug\n");
        printf("   This will set the debug option on\n\n");
        printf("Available options:\n");
        printf("    loadidentifier, osloadoptions, countdown\n");
}


int _cdecl main(
    IN int argc,
    IN char *argv[]
    )
{
    DWORD dwStatus;
    LPWSTR* pArgs;

    // parse command line in unicode

    pArgs= CommandLineToArgvW( GetCommandLine(), &argc );

    //
    // Get the boot information from boot.ini
    //

    // timeout

    dwStatus= GetPrivateProfileString(
                 STR_BOOTLDR, 
                 STR_TIMEOUT,
                 STR_NULL,
                 CountDown,
                 SIZECHARS(CountDown),
                 STR_BOOTINI );
    if( !dwStatus )
    {
        printf("Failed to get timeout value\n");
        return(-1);
    }

    // default os description and options

    dwStatus= GetPrivateProfileString(
                  STR_BOOTLDR,
                  STR_DEFAULT,
                  STR_NULL,
                  OsName,
                  SIZECHARS(OsName),
                  STR_BOOTINI );
    if( !dwStatus )
    {
        printf("Failed to get default OS name\n");
        return(-1);
    }

    dwStatus= GetPrivateProfileString(
                  STR_OPERATINGSYS,
                  OsName,
                  STR_NULL,
                  OsLine,
                  SIZECHARS(OsLine),
                  STR_BOOTINI );
    if( !dwStatus )
    {
        printf("Failed to get default os description\n");
        return(-1);
    }
                 
    //
    // Now parse the line into description and options.
    // If it starts with a quote, it may have options.
    // If it doesn't start with a quote, it won't.
    //

    *LoadID= *OsLoadOptions= TEXT('\0');

    if( *OsLine == TEXT('"') )
    {
        INT i;

        for( i=1; OsLine[i]; i++ )
        {
            LoadID[i-1]= OsLine[i];
            if( OsLine[i] == TEXT('"') )
               break;
        }

        if( OsLine[i] )
        {
            LoadID[i-1]= TEXT('\0');   // don't copy final quote
            lstrcpy( OsLoadOptions, &OsLine[i+1] );
            lstrcat( OsLoadOptions, TEXT(" ") ); // all options end with blank
        }
    }
    else
    {
        lstrcpy( LoadID, OsLine );
        lstrcpy( OsLoadOptions, TEXT("") );
    }

    // no parameters prints out values

    if( argc == 1 )
    {
        printf("%ws: %ws\n",PaddedBootVarNames[BootVarLoadIdentifier], LoadID);
        printf("%ws: %ws\n",PaddedBootVarNames[BootVarOsLoadOptions], OsLoadOptions);
        printf("%ws: %ws\n",PaddedBootVarNames[BootVarCountdown], CountDown);
    }
    
    // -set parameter = value
    // sets parameter to some value

    if( (argc == 2) &&
       !lstrcmpiW(pArgs[1],L"rotate") )
    {
        INT i;
        DWORD FileAttr;

        //
        // Read in all boot options
        //

        dwStatus= GetPrivateProfileString(
                      STR_OPERATINGSYS,
                      NULL,
                      STR_NULL,
                      OsLine,
                      SIZECHARS(OsLine),
                      STR_BOOTINI );
        if( !dwStatus )
        {
            printf("Failed to get os section\n");
            return(-1);
        }

        //
        // read through boot options until we find default entry
        //

        i = 0;

        while( lstrcmpiW( OsName, &(OsLine[i]) ) ){

            i = i + wcslen(&OsLine[i]) + 1;
        }

        //
        // increment one more entry
        //

        i = i + wcslen(&OsLine[i]) + 1;

        //
        // if we've gone off the end then start over
        //

        if (!lstrcmpiW( &(OsLine[i]), L"\0\0" ) ){
            i = 0;
        }

        //
        // Get file attributes of boot.ini for later restoration
        //

        FileAttr= GetFileAttributes( STR_BOOTINI );

        //
        // Change file attributes on boot.ini so we can write to it.
        //

        if( !SetFileAttributes( STR_BOOTINI, FILE_ATTRIBUTE_NORMAL ) )
        {
            printf("Failed to turn off read-only on boot.ini  (lasterr= %d)\n",
                    GetLastError() );
        }

        if( !WritePrivateProfileString( STR_BOOTLDR, STR_DEFAULT,
                                       &(OsLine[i]), STR_BOOTINI ) )
        {
            printf("failed to write default (lasterr= %d)\n",GetLastError());
        }

        //
        // Restore boot.ini file attributes
        //

        if( FileAttr != 0xFFFFFFFF )
        {
            SetFileAttributes( STR_BOOTINI, FileAttr );
        }

    }
    if( (argc == 5) && 
       !lstrcmpiW(pArgs[1]+1,L"set") && 
       !lstrcmpW(pArgs[3],L"=") )
    {
        INT i;

        // see if we understand parameter

        for( i=0; i<BootVarMax; i++ )
        {
            if( lstrcmpiW( pArgs[2], BootVarNames[i] ) == 0 )
                break;
        }

        // handle the ones we can

        switch( i )
        {
            default:
                printf("Not valid parameter name to set: %ws\n",pArgs[2]);
                Usage();
                return(-1);
                break;

            case BootVarLoadIdentifier:
                lstrcpyW( LoadID, pArgs[4] );
                break;

            case BootVarOsLoadOptions:
                HandleOption( pArgs[4] );
                break;

            case BootVarCountdown:
                lstrcpyW( CountDown, pArgs[4] );
                break;
        }

        WriteBootIni();
    }

    // -?     
    // usage message

    if( argc == 2 && !lstrcmpW(pArgs[1]+1, L"?") )
    {
        Usage();
    }

    return(0);
}

#endif