/*
 * Command line Registry implementation
 *
 * Copyright 1999 Sylvain St-Germain
 *
 * Note: Please consult the README file for more information.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winreg.h>
#include <winerror.h>
#include <winnt.h>
#include <string.h>

/******************************************************************************
 * Defines and consts
 */
#define IDENTICAL             0
#define COMMAND_COUNT         7

#define KEY_MAX_LEN             1024
#define STDIN_MAX_LEN         102400

/* Return values */
#define COMMAND_NOT_FOUND     -1
#define SUCCESS               0
#define NOT_ENOUGH_MEMORY     1
#define KEY_VALUE_ALREADY_SET 2
#define COMMAND_NOT_SUPPORTED 3

/* Generic global */
static BOOL bForce            = FALSE; /* Is set to TRUE when -force is
                                          passed on the command line */

/* Globals used by the api setValue, queryValue */
static LPSTR currentKeyName   = NULL;
static HKEY  currentKeyClass  = 0;
static HKEY  currentKeyHandle = 0;
static BOOL  bTheKeyIsOpen    = FALSE;

/* Delimiters used to parse the "value"="data" pair for setValue*/
#define SET_VALUE_MAX_ARGS    2
/* Delimiters used to parse the "value" to query queryValue*/
#define QUERY_VALUE_MAX_ARGS  1

static const char *setValueDelim[SET_VALUE_MAX_ARGS]   = {"=", ""};
static const char *queryValueDelim[QUERY_VALUE_MAX_ARGS]   = {""};


/*
 * Forward declaration
 */
typedef void (*commandAPI)(LPSTR lpsLine);

static void doSetValue(LPSTR lpsLine);
static void doDeleteValue(LPSTR lpsLine);
static void doCreateKey(LPSTR lpsLine);
static void doDeleteKey(LPSTR lpsLine);
static void doQueryValue(LPSTR lpsLine);
static void doRegisterDLL(LPSTR lpsLine);
static void doUnregisterDLL(LPSTR lpsLine);

/*
 * Currently supported api
 */
static const char* commandNames[COMMAND_COUNT] = {
  "setValue",
  "deleteValue",
  "createKey",
  "deleteKey",
  "queryValue",
  "registerDLL",
  "unregisterDLL"
};

/*
 * Pointers to processing entry points
 */
static const commandAPI commandAPIs[COMMAND_COUNT] = {
  doSetValue,
  doDeleteValue,
  doCreateKey,
  doDeleteKey,
  doQueryValue,
  doRegisterDLL,
  doUnregisterDLL
};

/*
 * This array controls the registry saving needs at the end of the process
 */
static const BOOL commandSaveRegistry[COMMAND_COUNT] = {
  TRUE,
  TRUE,
  TRUE,
  TRUE,
  FALSE,
  TRUE,
  TRUE
};

/*
 * Generic prototypes
 */
static DWORD   getDataType(LPSTR *lpValue, DWORD* parse_type);
static LPSTR   getRegKeyName(LPSTR lpLine);
static HKEY    getRegClass(LPSTR lpLine);
static LPSTR   getArg(LPSTR arg);
static INT     getCommand(LPSTR commandName);
static DWORD   convertHexToDWord(char *str, BYTE *buf);
static DWORD   convertHexCSVToHex(char *str, BYTE *buf, ULONG bufLen);
static LPSTR   convertHexToHexCSV( BYTE *buf, ULONG len);
static LPSTR   convertHexToDWORDStr( BYTE *buf, ULONG len);
static HRESULT openKey(LPSTR stdInput);
static void    closeKey();

/*
 * api setValue prototypes
 */
static void    processSetValue(LPSTR cmdline);
static HRESULT setValue(LPSTR *argv);

/*
 * api queryValue prototypes
 */
static void    processQueryValue(LPSTR cmdline);

/*
 * Help Text displayed when invalid parameters are provided
 */
static char helpText[] =
"NAME\n"
"          regapi - perform certain actions on the wine registry.\n"
"\n"
"SYNOPSIS\n"
"          regapi commandName [-force] < file\n"
"\n"
"DESCRIPTION\n"
"          regapi modifies settings in the wine registry.  It processes\n"
"          the given commandName for every line in the stdin data stream.\n"
"          Input data format may vary depending on the commandName\n"
"          (see INPUT FILE FORMAT).\n"
"\n"
"OPTIONS\n"
"          commandName\n"
"              Instruct regapi about what action to perform on the data stream.\n"
"              Currently, only setValue and queryValue are supported and\n"
"              implemented.\n"
"\n"
"          -force\n"
"              When provided the action will be performed anyway.  This may\n"
"              have a different meaning depending on the context.  For example,\n"
"              when providing -force to setValue, the value is set even if it\n"
"              was previously set to another value.\n"
"\n"
"          < file\n"
"              STDIN channel, provide a file name with line of the appropriate\n"
"              format.\n"
"\n"
"INPUT FILE FORMAT\n"
"\n"
"          setValue\n"
"              The input file is in format, obtained from regedit.exe export.\n"
"\n"
"              [KEY_CLASS\\Some\\Path\\For\\A\\Key]\n"
"              \"Value1\"=\"Data1\"\n"
"              \"Value2\"=\"Data2\"\n"
"              \"Valuen\"=\"Datan\"\n"
"              ...\n"
"\n"
"          queryValue\n"
"              The input file format required by the queryValue command is\n"
"              similar to the one required by setValue.  The only\n"
"              difference is that you only provide the value name.\n"
"\n"
"              [KEY_CLASS\\Some\\Path\\For\\A\\Key]\n"
"              \"Value1\"\n"
"              \"Value2\"\n"
"              \"Valuen\"\n"
"              ...\n"
"           registerDLL\n"
"               The input file format is a list of DLLs to register\n"
"\n"
"           unregisterDLL\n"
"               The input file format is a list of DLLs to unregister\n"
"                                February 1999.\n"
;


/******************************************************************************
 * This function returns the HKEY associated with the data type encoded in the
 * value.  It modifies the input parameter (key value) in order to skip this
 * "now useless" data type information.
 *
 * Note: Updated based on the algorithm used in 'server/registry.c'
 */
DWORD getDataType(LPSTR *lpValue, DWORD* parse_type)
{
    struct data_type { const char *tag; int len; int type; int parse_type; };

    static const struct data_type data_types[] =
    {                   /* actual type */  /* type to assume for parsing */
        { "\"",        1,   REG_SZ,              REG_SZ },
        { "str:\"",    5,   REG_SZ,              REG_SZ },
        { "str(2):\"", 8,   REG_EXPAND_SZ,       REG_SZ },
        { "str(7):\"", 8,   REG_MULTI_SZ,        REG_SZ },
        { "hex:",      4,   REG_BINARY,          REG_BINARY },
        { "dword:",    6,   REG_DWORD,           REG_DWORD },
        { "hex(",      4,   -1,                  REG_BINARY },
        { NULL,        0,    0,                  0 }
    };

    const struct data_type *ptr;
    int type;

    for (ptr = data_types; ptr->tag; ptr++)
    {
        if (memcmp( ptr->tag, *lpValue, ptr->len ))
            continue;

        /* Found! */
        *parse_type = ptr->parse_type;
        type=ptr->type;
        *lpValue+=ptr->len;
        if (type == -1) {
            char* end;
            /* "hex(xx):" is special */
            *lpValue += 4;
            type = (int)strtoul( *lpValue , &end, 16 );
            if (**lpValue=='\0' || *end!=')' || *(end+1)!=':') {
                type=REG_NONE;
            } else {
                *lpValue=end+2;
            }
        }
        return type;
    }
    return (**lpValue=='\0'?REG_SZ:REG_NONE);
}

/******************************************************************************
 * Extracts from a [HKEY\some\key\path] type of line the key name (what starts
 * after the first '\' and end before the ']'
 */
LPSTR getRegKeyName(LPSTR lpLine)
{
  LPSTR keyNameBeg = NULL;
  LPSTR keyNameEnd = NULL;
  char  lpLineCopy[KEY_MAX_LEN];

  if (lpLine == NULL)
    return NULL;

  strcpy(lpLineCopy, lpLine);

  keyNameBeg  = strstr(lpLineCopy, "\\");  /* The key name start by '\' */
  keyNameBeg++;                            /* but is not part of the key name */
  keyNameEnd  = strstr(lpLineCopy, "]");   /* The key name end by ']' */
  *keyNameEnd = '\0';                      /* Isolate the key name */

  currentKeyName = HeapAlloc(GetProcessHeap(), 0, strlen(keyNameBeg)+1);
  if (currentKeyName != NULL)
    strcpy(currentKeyName, keyNameBeg);

  return currentKeyName;
}

/******************************************************************************
 * Extracts from a [HKEY/some/key/path] type of line the key class (what
 * starts after the '[' and ends before the first '\'
 */
static HKEY getRegClass(LPSTR lpClass)
{
  LPSTR classNameEnd;
  LPSTR classNameBeg;

  char  lpClassCopy[KEY_MAX_LEN];

  if (lpClass == NULL)
    return (HKEY)ERROR_INVALID_PARAMETER;

  strcpy(lpClassCopy, lpClass);

  classNameEnd  = strstr(lpClassCopy, "\\");  /* The class name end by '\' */
  *classNameEnd = '\0';                       /* Isolate the class name */
  classNameBeg  = &lpClassCopy[1];            /* Skip the '[' */

  if      (strcmp( classNameBeg, "HKEY_LOCAL_MACHINE") == IDENTICAL )
    return  HKEY_LOCAL_MACHINE;
  else if (strcmp( classNameBeg, "HKEY_USERS") == IDENTICAL )
    return  HKEY_USERS;
  else if (strcmp( classNameBeg, "HKEY_CLASSES_ROOT") == IDENTICAL )
    return  HKEY_CLASSES_ROOT;
  else if (strcmp( classNameBeg, "HKEY_CURRENT_CONFIG") == IDENTICAL )
    return  HKEY_CURRENT_CONFIG;
  else if (strcmp( classNameBeg, "HKEY_CURRENT_USER") == IDENTICAL )
    return  HKEY_CURRENT_USER;
  else
    return (HKEY)ERROR_INVALID_PARAMETER;
}

/******************************************************************************
 * This is a replacement for strsep which is not portable (missing on Solaris).
 */
static char* getToken(char** str, const char* delims)
{
    char* token;

    if (*str==NULL) {
        /* No more tokens */
        return NULL;
    }

    token=*str;
    while (**str!='\0') {
        if (strchr(delims,**str)!=NULL) {
            **str='\0';
            (*str)++;
            return token;
        }
        (*str)++;
    }
    /* There is no other token */
    *str=NULL;
    return token;
}

/******************************************************************************
 * Returns an allocated buffer with a cleaned copy (removed the surrounding
 * dbl quotes) of the passed value.
 */
static LPSTR getArg( LPSTR arg)
{
  LPSTR tmp = NULL;
  ULONG len;

  if (arg == NULL)
    return NULL;

  /*
   * Get rid of surrounding quotes
   */
  len = strlen(arg);

  if( arg[len-1] == '\"' ) arg[len-1] = '\0';
  if( arg[0]     == '\"' ) arg++;

  tmp = HeapAlloc(GetProcessHeap(), 0, strlen(arg)+1);
  strcpy(tmp, arg);

  return tmp;
}

/******************************************************************************
 * Returns the index in the commands array of the command to process.
 */
static INT getCommand(LPSTR commandName)
{
  INT count;
  for (count=0; count < COMMAND_COUNT; count++)
    if ( strcmp(commandName, commandNames[count]) == IDENTICAL)
      return count;

  return COMMAND_NOT_FOUND;
}

/******************************************************************************
 * Converts a hex representation of a DWORD into a DWORD.
 */
static DWORD convertHexToDWord(char *str, BYTE *buf)
{
  DWORD dw;
  char xbuf[9];

  memcpy(xbuf,str,8);
  xbuf[8]='\0';
  sscanf(xbuf,"%08lx",&dw);
  memcpy(buf,&dw,sizeof(DWORD));
  return sizeof(DWORD);
}

/******************************************************************************
 * Converts a hex buffer into a hex comma separated values
 */
static char* convertHexToHexCSV(BYTE *buf, ULONG bufLen)
{
  char* str;
  char* ptrStr;
  BYTE* ptrBuf;

  ULONG current = 0;

  str    = HeapAlloc(GetProcessHeap(), 0, (bufLen+1)*2);
  memset(str, 0, (bufLen+1)*2);
  ptrStr = str;  /* Pointer to result  */
  ptrBuf = buf;  /* Pointer to current */

  while (current < bufLen)
  {
    BYTE bCur = ptrBuf[current++];
    char res[3];

    sprintf(res, "%02x", (unsigned int)*&bCur);
    strcat(str, res);
    strcat(str, ",");
  }

  /* Get rid of the last comma */
  str[strlen(str)-1] = '\0';
  return str;
}

/******************************************************************************
 * Converts a hex buffer into a DWORD string
 */
static char* convertHexToDWORDStr(BYTE *buf, ULONG bufLen)
{
  char* str;
  DWORD dw;

  if ( bufLen != sizeof(DWORD) ) return NULL;

  str = HeapAlloc(GetProcessHeap(), 0, (bufLen*2)+1);

  memcpy(&dw,buf,sizeof(DWORD));
  sprintf(str, "%08lx", dw);

  /* Get rid of the last comma */
  return str;
}
/******************************************************************************
 * Converts a hex comma separated values list into a hex list.
 */
static DWORD convertHexCSVToHex(char *str, BYTE *buf, ULONG bufLen)
{
  char *s = str;  /* Pointer to current */
  char *b = buf;  /* Pointer to result  */

  ULONG strLen    = strlen(str);
  ULONG strPos    = 0;
  DWORD byteCount = 0;

  memset(buf, 0, bufLen);

  /*
   * warn the user if we are here with a string longer than 2 bytes that does
   * not contains ",".  It is more likely because the data is invalid.
   */
  if ( ( strlen(str) > 2) && ( strstr(str, ",") == NULL) )
    printf("regapi: WARNING converting CSV hex stream with no comma, "
           "input data seems invalid.\n");

  while (strPos < strLen)
  {
    char xbuf[3];
    char wc;

    memcpy(xbuf,s,2); xbuf[3]='\0';
    sscanf(xbuf,"%02x",(UINT*)&wc);
    *b++ =(unsigned char)wc;

    s+=3;
    strPos+=3;
    byteCount++;
  }

  return byteCount;
}


/******************************************************************************
 * Sets the value in argv[0] to the data in argv[1] for the currently
 * opened key.
 */
static HRESULT setValue(LPSTR *argv)
{
  HRESULT hRes;
  DWORD   dwSize          = KEY_MAX_LEN;
  DWORD   dwType          = 0;
  DWORD   dwDataType,dwParseType;

  LPSTR   lpsCurrentValue;

  LPSTR   keyValue = getArg(argv[0]);
  LPSTR   keyData  = argv[1];

  /* Make some checks */
  if ( (keyValue == NULL) || (keyData == NULL) )
    return ERROR_INVALID_PARAMETER;

  lpsCurrentValue=HeapAlloc(GetProcessHeap(), 0,KEY_MAX_LEN);
  /*
   * Default registry values are encoded in the input stream as '@' but as
   * blank in the wine registry.
   */
  if( (keyValue[0] == '@') && (strlen(keyValue) == 1) )
    keyValue[0] = '\0';

  /* Get the data type stored into the value field */
  dwDataType = getDataType(&keyData,&dwParseType);

  memset(lpsCurrentValue, 0, KEY_MAX_LEN);
  hRes = RegQueryValueExA(
          currentKeyHandle,
          keyValue,
          NULL,
          &dwType,
          (LPBYTE)lpsCurrentValue,
          &dwSize);

  while(hRes==ERROR_MORE_DATA){
      dwSize+=KEY_MAX_LEN;
      lpsCurrentValue=HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,lpsCurrentValue,dwSize);
      hRes = RegQueryValueExA(currentKeyHandle,keyValue,NULL,&dwType,(LPBYTE)lpsCurrentValue,&dwSize);
  }

  if( ( strlen(lpsCurrentValue) == 0 ) ||  /* The value is not existing */
      ( bForce ))         /* -force option */
  {
    LPBYTE lpbData;
    BYTE   convert[KEY_MAX_LEN];
    DWORD  dwLen;

    if ( dwParseType == REG_SZ)        /* no conversion for string */
    {
      dwLen   = strlen(keyData);
      if (dwLen>0 && keyData[dwLen-1]=='"')
      {
        dwLen--;
        keyData[dwLen]='\0';
      }
      lpbData = keyData;
    }
    else if (dwParseType == REG_DWORD)  /* Convert the dword types */
    {
      dwLen   = convertHexToDWord(keyData, convert);
      lpbData = convert;
    }
    else                               /* Convert the hexadecimal types */
    {
      dwLen   = convertHexCSVToHex(keyData, convert, KEY_MAX_LEN);
      lpbData = convert;
    }

    hRes = RegSetValueEx(
            currentKeyHandle,
            keyValue,
            0,                  /* Reserved */
            dwDataType,
            lpbData,
            dwLen);
  }
  else
  {
    /* return the current value data into argv[1] */
    if (argv[1] != NULL)
    {
      HeapFree(GetProcessHeap(), 0, argv[1]);
      argv[1] = HeapAlloc(GetProcessHeap(), 0, dwSize+1);

      if ( argv[1] != NULL ) {
        strncpy(argv[1], lpsCurrentValue, dwSize);
        argv[1][dwSize]='\0';
      }
    }

    hRes=KEY_VALUE_ALREADY_SET;
  }
  if (keyValue != NULL)
      HeapFree(GetProcessHeap(), 0, keyValue);
  return hRes;
}


/******************************************************************************
 * Open the key
 */
static HRESULT openKey( LPSTR stdInput)
{
  DWORD   dwDisp;
  HRESULT hRes;

  /* Sanity checks */
  if (stdInput == NULL)
    return ERROR_INVALID_PARAMETER;

  /* Get the registry class */
  currentKeyClass = getRegClass(stdInput); /* Sets global variable */
  if (currentKeyClass == (HKEY)ERROR_INVALID_PARAMETER)
    return (HRESULT)ERROR_INVALID_PARAMETER;

  /* Get the key name */
  currentKeyName = getRegKeyName(stdInput); /* Sets global variable */
  if (currentKeyName == NULL)
    return ERROR_INVALID_PARAMETER;

  hRes = RegCreateKeyEx(
          currentKeyClass,          /* Class     */
          currentKeyName,           /* Sub Key   */
          0,                        /* MUST BE 0 */
          NULL,                     /* object type */
          REG_OPTION_NON_VOLATILE,  /* option, REG_OPTION_NON_VOLATILE ... */
          KEY_ALL_ACCESS,           /* access mask, KEY_ALL_ACCESS */
          NULL,                     /* security attribute */
          &currentKeyHandle,        /* result */
          &dwDisp);                 /* disposition, REG_CREATED_NEW_KEY or
                                                    REG_OPENED_EXISTING_KEY */

  if (hRes == ERROR_SUCCESS)
    bTheKeyIsOpen = TRUE;

  return hRes;

}
/******************************************************************************
 * This function is a wrapper for the setValue function.  It prepares the
 * land and clean the area once completed.
 */
static void processSetValue(LPSTR cmdline)
{
  LPSTR argv[SET_VALUE_MAX_ARGS];  /* args storage    */

  LPSTR token         = NULL;      /* current token analized */
  ULONG argCounter    = 0;         /* counter of args */
  INT   counter;
  HRESULT hRes = 0;

  /*
   * Init storage and parse the line
   */
  for (counter=0; counter<SET_VALUE_MAX_ARGS; counter++)
    argv[counter]=NULL;

  while( (token = getToken(&cmdline, setValueDelim[argCounter])) != NULL )
  {
    argv[argCounter++] = token;

    if (argCounter == SET_VALUE_MAX_ARGS)
      break;  /* Stop processing args no matter what */
  }

  hRes = setValue(argv);
  if ( hRes == ERROR_SUCCESS )
    printf(
      "regapi: Value \"%s\" has been set to \"%s\" in key [%s]\n",
      argv[0],
      argv[1],
      currentKeyName);

  else if ( hRes == KEY_VALUE_ALREADY_SET )
    printf(
      "regapi: Value \"%s\" already set to \"%s\" in key [%s]\n",
      argv[0],
      argv[1],
      currentKeyName);

  else
    printf("regapi: ERROR Key %s not created. Value: %s, Data: %s\n",
      currentKeyName,
      argv[0],
      argv[1]);
}

/******************************************************************************
 * This function is a wrapper for the queryValue function.  It prepares the
 * land and clean the area once completed.
 */
static void processQueryValue(LPSTR cmdline)
{
  LPSTR   argv[QUERY_VALUE_MAX_ARGS];/* args storage    */
  LPSTR   token      = NULL;         /* current token analized */
  ULONG   argCounter = 0;            /* counter of args */
  INT     counter;
  HRESULT hRes       = 0;
  LPSTR   keyValue   = NULL;
  LPSTR   lpsRes     = NULL;

  /*
   * Init storage and parse the line
   */
  for (counter=0; counter<QUERY_VALUE_MAX_ARGS; counter++)
    argv[counter]=NULL;

  while( (token = getToken(&cmdline, queryValueDelim[argCounter])) != NULL )
  {
    argv[argCounter++] = getArg(token);

    if (argCounter == QUERY_VALUE_MAX_ARGS)
      break;  /* Stop processing args no matter what */
  }

  /* The value we look for is the first token on the line */
  if ( argv[0] == NULL )
    return; /* SHOULD NOT HAPPEN */
  else
    keyValue = argv[0];

  if( (keyValue[0] == '@') && (strlen(keyValue) == 1) )
  {
    LONG  lLen  = KEY_MAX_LEN;
    CHAR*  lpsData=HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,KEY_MAX_LEN);
    /*
     * We need to query the key default value
     */
    hRes = RegQueryValue(
             currentKeyHandle,
             currentKeyName,
             (LPBYTE)lpsData,
             &lLen);

    if (hRes==ERROR_MORE_DATA) {
        lpsData=HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,lpsData,lLen);
        hRes = RegQueryValue(currentKeyHandle,currentKeyName,(LPBYTE)lpsData,&lLen);
    }

    if (hRes == ERROR_SUCCESS)
    {
      lpsRes = HeapAlloc( GetProcessHeap(), 0, lLen);
      strncpy(lpsRes, lpsData, lLen);
      lpsRes[lLen-1]='\0';
    }
  }
  else
  {
    DWORD  dwLen  = KEY_MAX_LEN;
    BYTE*  lpbData=HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,KEY_MAX_LEN);
    DWORD  dwType;
    /*
     * We need to query a specific value for the key
     */
    hRes = RegQueryValueEx(
             currentKeyHandle,
             keyValue,
             0,
             &dwType,
             (LPBYTE)lpbData,
             &dwLen);

    if (hRes==ERROR_MORE_DATA) {
        lpbData=HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,lpbData,dwLen);
        hRes = RegQueryValueEx(currentKeyHandle,keyValue,NULL,&dwType,(LPBYTE)lpbData,&dwLen);
    }

    if (hRes == ERROR_SUCCESS)
    {
      /*
       * Convert the returned data to a displayable format
       */
      switch ( dwType )
      {
        case REG_SZ:
        case REG_EXPAND_SZ:
        {
          lpsRes = HeapAlloc( GetProcessHeap(), 0, dwLen);
          strncpy(lpsRes, lpbData, dwLen);
          lpsRes[dwLen-1]='\0';
          break;
        }
        case REG_DWORD:
        {
          lpsRes = convertHexToDWORDStr(lpbData, dwLen);
          break;
        }
        default:
        {
          lpsRes = convertHexToHexCSV(lpbData, dwLen);
          break;
        }
      }
    }

    HeapFree(GetProcessHeap(), 0, lpbData);
  }


  if ( hRes == ERROR_SUCCESS )
    printf(
      "regapi: Value \"%s\" = \"%s\" in key [%s]\n",
      keyValue,
      lpsRes,
      currentKeyName);

  else
    printf("regapi: ERROR Value \"%s\" not found. for key \"%s\"\n",
      keyValue,
      currentKeyName);

  /*
   * Do some cleanup
   */
  for (counter=0; counter<argCounter; counter++)
    if (argv[counter] != NULL)
      HeapFree(GetProcessHeap(), 0, argv[counter]);

  if (lpsRes != NULL)
    HeapFree(GetProcessHeap(), 0, lpsRes);

}

/******************************************************************************
 * Close the currently opened key.
 */
static void closeKey()
{
  RegCloseKey(currentKeyHandle);

  HeapFree(GetProcessHeap(), 0, currentKeyName); /* Allocated by getKeyName */

  bTheKeyIsOpen    = FALSE;

  currentKeyName   = NULL;
  currentKeyClass  = 0;
  currentKeyHandle = 0;
}

/******************************************************************************
 * This funtion is the main entry point to the setValue type of action.  It
 * receives the currently read line and dispatch the work depending on the
 * context.
 */
static void doSetValue(LPSTR stdInput)
{
  /*
   * We encoutered the end of the file, make sure we
   * close the opened key and exit
   */
  if (stdInput == NULL)
  {
    if (bTheKeyIsOpen != FALSE)
      closeKey();

    return;
  }

  if      ( stdInput[0] == '[')      /* We are reading a new key */
  {
    if ( bTheKeyIsOpen != FALSE )
      closeKey();                    /* Close the previous key before */

    if ( openKey(stdInput) != ERROR_SUCCESS )
      printf ("regapi: doSetValue failed to open key %s\n", stdInput);
  }
  else if( ( bTheKeyIsOpen ) &&
           (( stdInput[0] == '@') || /* reading a default @=data pair */
            ( stdInput[0] == '\"'))) /* reading a new value=data pair */
  {
    processSetValue(stdInput);
  }
  else                               /* since we are assuming that the */
  {                                  /* file format is valid we must   */
    if ( bTheKeyIsOpen )             /* be reading a blank line which  */
      closeKey();                    /* indicate end of this key processing */
  }
}

/******************************************************************************
 * This funtion is the main entry point to the queryValue type of action.  It
 * receives the currently read line and dispatch the work depending on the
 * context.
 */
static void doQueryValue(LPSTR stdInput) {
  /*
   * We encoutered the end of the file, make sure we
   * close the opened key and exit
   */
  if (stdInput == NULL)
  {
    if (bTheKeyIsOpen != FALSE)
      closeKey();

    return;
  }

  if      ( stdInput[0] == '[')      /* We are reading a new key */
  {
    if ( bTheKeyIsOpen != FALSE )
      closeKey();                    /* Close the previous key before */

    if ( openKey(stdInput) != ERROR_SUCCESS )
      printf ("regapi: doSetValue failed to open key %s\n", stdInput);
  }
  else if( ( bTheKeyIsOpen ) &&
           (( stdInput[0] == '@') || /* reading a default @=data pair */
            ( stdInput[0] == '\"'))) /* reading a new value=data pair */
  {
    processQueryValue(stdInput);
  }
  else                               /* since we are assuming that the */
  {                                  /* file format is valid we must   */
    if ( bTheKeyIsOpen )             /* be reading a blank line which  */
      closeKey();                    /* indicate end of this key processing */
  }
}

/******************************************************************************
 * This funtion is the main entry point to the deletetValue type of action.  It
 * receives the currently read line and dispatch the work depending on the
 * context.
 */
static void doDeleteValue(LPSTR line) {
  printf ("regapi: deleteValue not yet implemented\n");
}
/******************************************************************************
 * This funtion is the main entry point to the deleteKey type of action.  It
 * receives the currently read line and dispatch the work depending on the
 * context.
 */
static void doDeleteKey(LPSTR line)   {
  printf ("regapi: deleteKey not yet implemented\n");
}
/******************************************************************************
 * This funtion is the main entry point to the createKey type of action.  It
 * receives the currently read line and dispatch the work depending on the
 * context.
 */
static void doCreateKey(LPSTR line)   {
  printf ("regapi: createKey not yet implemented\n");
}

/******************************************************************************
 * This funtion is the main entry point to the registerDLL action.  It
 * receives the currently read line, then loads and registers the requested DLLs
 */
static void doRegisterDLL(LPSTR stdInput) {
  HMODULE theLib = 0;
  UINT retVal = 0;

  /* Check for valid input */
  if (stdInput == NULL)
    return;

  /* Load and register the library, then free it */
  theLib = LoadLibrary(stdInput);
  if (theLib)
  {
    FARPROC lpfnDLLRegProc = GetProcAddress(theLib, "DllRegisterServer");
    if (lpfnDLLRegProc)
      retVal = (*lpfnDLLRegProc)();
    else
      printf("regapi: Couldn't find DllRegisterServer proc in '%s'.\n", stdInput);

    if (retVal != S_OK)
      printf("regapi: DLLRegisterServer error 0x%x in '%s'.\n", retVal, stdInput);

    FreeLibrary(theLib);
  }
  else
  {
    printf("regapi: Could not load DLL '%s'.\n", stdInput);
  }
}

/******************************************************************************
 * This funtion is the main entry point to the unregisterDLL action.  It
 * receives the currently read line, then loads and unregisters the requested DLLs
 */
static void doUnregisterDLL(LPSTR stdInput) {
  HMODULE theLib = 0;
  UINT retVal = 0;

  /* Check for valid input */
  if (stdInput == NULL)
    return;

  /* Load and unregister the library, then free it */
  theLib = LoadLibrary(stdInput);
  if (theLib)
  {
    FARPROC lpfnDLLRegProc = GetProcAddress(theLib, "DllUnregisterServer");
    if (lpfnDLLRegProc)
      retVal = (*lpfnDLLRegProc)();
    else
      printf("regapi: Couldn't find DllUnregisterServer proc in '%s'.\n", stdInput);

    if (retVal != S_OK)
      printf("regapi: DLLUnregisterServer error 0x%x in '%s'.\n", retVal, stdInput);

    FreeLibrary(theLib);
  }
  else
  {
    printf("regapi: Could not load DLL '%s'.\n", stdInput);
  }
}

/******************************************************************************
 * MAIN - WinMain simply validates the first parameter (command to perform)
 *        It then reads the STDIN line by line forwarding their processing
 *        to the appropriate method.
 */
int PASCAL WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
{
  LPSTR  token          = NULL;  /* current token analized */
  LPSTR  stdInput       = NULL;  /* line read from stdin */
  INT    cmdIndex       = -1;    /* index of the command in array */
  LPSTR nextLine        = NULL;
  ULONG  currentSize    = STDIN_MAX_LEN;

  stdInput = HeapAlloc(GetProcessHeap(), 0, STDIN_MAX_LEN);
  nextLine = HeapAlloc(GetProcessHeap(), 0, STDIN_MAX_LEN);

  if (stdInput == NULL || nextLine== NULL)
    return NOT_ENOUGH_MEMORY;

  /*
   * get the command, should be the first arg (modify cmdLine)
   */
  token = getToken(&cmdline, " ");
  if (token != NULL)
  {
    cmdIndex = getCommand(token);
    if (cmdIndex == COMMAND_NOT_FOUND)
    {
      printf("regapi: Command \"%s\" is not supported.\n", token);
      printf(helpText);
      return COMMAND_NOT_SUPPORTED;
    }
  }
  else
  {
    printf(
      "regapi: The first item on the command line must be the command name.\n");
    printf(helpText);
    return COMMAND_NOT_SUPPORTED;
  }

  /*
   * check to see wether we force the action
   * (meaning differs depending on the command performed)
   */
  if ( cmdline != NULL ) /* will be NULL if '-force' is not provided */
    if ( strstr(cmdline, "-force") != NULL )
      bForce = TRUE;

  printf("Processing stdin...\n");

  while ( TRUE )
  {
    /*
     * read a line
     */
      ULONG curSize=STDIN_MAX_LEN;
      char* s=NULL;

      while((NULL!=(stdInput=fgets(stdInput,curSize,stdin))) && (NULL==(s=strchr(stdInput,'\n'))) ){
          fseek(stdin,-curSize,SEEK_CUR+1);
          stdInput=HeapReAlloc(GetProcessHeap(), 0,stdInput,curSize+=STDIN_MAX_LEN);
      }
    /*
     * Make some handy generic stuff here...
     */
    if ( stdInput != NULL )
    {
      stdInput[strlen(stdInput) -1] = '\0'; /* get rid of new line */

      if( stdInput[0] == '#' )              /* this is a comment, skip */
        continue;

      while( stdInput[strlen(stdInput) -1] == '\\' ){ /* a '\' char in the end of the current line means  */
                                                      /* that this line is not complete and we have to get */
          stdInput[strlen(stdInput) -1]= '\0';        /* the rest in the next lines */

          nextLine = fgets(nextLine, STDIN_MAX_LEN, stdin);

          nextLine[strlen(nextLine)-1] = '\0';

          if ( (strlen(stdInput)+strlen(nextLine)) > currentSize){

              stdInput=HeapReAlloc(GetProcessHeap(),0,stdInput,strlen(stdInput)+STDIN_MAX_LEN);

              currentSize+=STDIN_MAX_LEN;
          }

          strcat(stdInput,nextLine+2);
      }

    }

    /*
     * We process every lines even the NULL (last) line, to indicate the
     * end of the processing to the specific process.
     */
    commandAPIs[cmdIndex](stdInput);

    if (stdInput == NULL)  /* EOF encountered */
      break;
  }

#if 0
  /*
   * Save the registry only if it was modified
   */
 if ( commandSaveRegistry[cmdIndex] != FALSE )
       SHELL_SaveRegistry();
#endif
  HeapFree(GetProcessHeap(), 0, nextLine);

  HeapFree(GetProcessHeap(), 0, stdInput);

  return SUCCESS;
}
