//+----------------------------------------------------------------------------
//
//  Scheduling Agent Service
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1996.
//
//  File:       sageset.cxx
//
//  Contents:   Support for Sage Settings
//
//  History:    18-Jul-96 EricB created
//              3-06-1997   DavidMun   added code to create sagerun argument
//              4-16-1997   DavidMun   made it use strings from UI instead of
//                                      from job object.
//
//-----------------------------------------------------------------------------

#include "..\pch\headers.hxx"
#pragma hdrstop
#include <mstask.h>
#include <job_cls.hxx>
#include <debug.hxx>
#include <sage.hxx>
#include "sageset.hxx"
#include "..\inc\misc.hxx"
#include "..\folderui\macros.h" // get ARRAYLEN macro

const CHAR SAGE_KEY[] = "SOFTWARE\\Microsoft\\Plus!\\System Agent\\SAGE";
const CHAR PROGRAM_VALUE[] ="Program";
const CHAR SETTINGS_VALUE[] = "Settings";
const CHAR SAGESET_PARAM[] = " /SAGESET:";

BOOL GetAppNameFromPath(CHAR * pszFullPathName, CHAR * pszAppName);
BOOL AppNamesMatch(CHAR * pszCommand1, CHAR * pszCommand2);
BOOL fAppCantSupportMultipleInstances(CHAR * pszCmd);
BOOL AnsiToIntA(LPCSTR pszString, int * piRet);
HRESULT GetNextSageRunParam(HKEY hkSageApp, PINT pnSetNum);


//+----------------------------------------------------------------------------
//
//  Function:   DoSageSettings
//
//  Synopsis:   Invoke the Sage-aware app to alter its schedule settings.
//
//  Arguments:  [szCommand] - app name portion of command line
//              [szSageRun] - "" or contains /SAGERUN switch on input.
//                              contains /SAGERUN switch on output if app is
//                              sage-aware.
//
//  Modifies:   *[szSageRun]
//
//  Returns:    S_OK    - application launched
//              S_FALSE - application doesn't support sage settings
//              E_*
//
//  History:    3-06-1997   DavidMun   Added [szNewArg] & [cchNewArg].
//              4-16-1997   DavidMun   Take strings from UI instead of job
//                                       object.
//
//-----------------------------------------------------------------------------

HRESULT
DoSageSettings(
    LPSTR szCommand,
    LPSTR szSageRun)
{          
    int  nSetNum = 0;
                                                                                 
    if (!IsSageAware(szCommand, szSageRun, &nSetNum))
    {
        return S_FALSE;
    }

    //
    // We are here because the user hit the "Settings..." button, so if the
    // arguments don't include a sageset parameter, we need to add it.
    //

    if (!*szSageRun)
    {
        //
        // IsSageAware set nSetNum to the next valid number to use.
        //

        wsprintf(szSageRun, "%s%u", SAGERUN_PARAM, nSetNum);
    }

    //
    // Create a command line to invoke the sage-aware app with the sageset
    // parameter.
    //

    CHAR szSetCommand[MAX_PATH];
    CHAR szSetParams[MAX_PATH];

    lstrcpyn(szSetCommand, szCommand, MAX_PATH);
    lstrcpy(szSetParams, SAGESET_PARAM);

    wsprintf(&szSetParams[lstrlen(szSetParams)], "%u", nSetNum);
                           
    if (!GetAppNameFromPath(szSetCommand, NULL)) //got a path?
    {
        //
        // GetAppNameFromPath returns FALSE if szSetCommand is not a full 
        // path.  So, see if the app has registered a path. If it has, 
        // GetAppPath places the full path name in szFullyQualified.
        //
        CHAR szFullyQualified[MAX_PATH];

        GetAppPathInfo(szSetCommand,
                       szFullyQualified,
                       MAX_PATH,
                       NULL,
                       0);

        if (*szFullyQualified)
        {
            lstrcpy(szSetCommand, szFullyQualified);
        }
    }

    //
    // Prefix the system path with the directories listed by the application
    // in the app paths key.
    //

    BOOL  fChangedPath;
    LPSTR pszSavedPath;

    fChangedPath = SetAppPath(szSetCommand, &pszSavedPath);

    //
    // Put the SageSet param onto the command line.
    //
    if (szSetParams[0] != ' ')
    {
        lstrcat(szSetCommand, " ");
    }
    lstrcat(szSetCommand, szSetParams);

    DWORD dwErr = ERROR_SUCCESS;
    STARTUPINFO sui;
    PROCESS_INFORMATION pi;
    ZeroMemory(&sui, sizeof(sui));
    sui.cb = sizeof (STARTUPINFO);
                  
    if (CreateProcess(NULL,
                      szSetCommand,
                      NULL,
                      NULL,
                      FALSE,
                      CREATE_NEW_CONSOLE |
                      CREATE_NEW_PROCESS_GROUP,
                      NULL,
                      NULL,
                      &sui,
                      &pi))
    {
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }
    else 
    {
        dwErr = GetLastError();
        ERR_OUT("DoSageSettings: CreateProcess", dwErr);
    }

    if (fChangedPath)
    {
        SetEnvironmentVariable(TEXT("PATH"), pszSavedPath);
        delete [] pszSavedPath;
    }

    return (dwErr == ERROR_SUCCESS) ? S_OK : HRESULT_FROM_WIN32(dwErr);
}

//+--------------------------------------------------------------------------
//
//  Function:   IsSageAwareW
//
//  Synopsis:   Unicode wrapper for IsSageAware
//
//  History:    10-27-1997   DavidMun   Created
//
//---------------------------------------------------------------------------

BOOL
IsSageAwareW(WCHAR *pwzCmd, WCHAR *pwzParams, int *pnSetNum)
{
    HRESULT hr;
    CHAR    szCmd[MAX_PATH+1];
    CHAR    szParam[MAX_PATH+1];
    BOOL    fResult = FALSE;

    do
    {
        hr = UnicodeToAnsi(szCmd, pwzCmd, ARRAYLEN(szCmd));
        BREAK_ON_FAIL(hr);

        hr = UnicodeToAnsi(szParam, pwzParams, ARRAYLEN(szParam));
        BREAK_ON_FAIL(hr);

        fResult = IsSageAware(szCmd, szParam, pnSetNum);
    } while (0);

    return fResult;
}


//+----------------------------------------------------------------------------
//
//  Function:   IsSageAware
//
//  Synopsis:   Does this app use Sage settings?
//
//  Arguments:  [pszCmd] - The task application name property.
//              [pszParams] - The task parameters, can be NULL if pnSetNum is
//                            NULL.
//              [pnSetNum] - the registry setting set number to return, can
//                           be NULL if not needed.
//
//  Returns:    TRUE if it does, FALSE otherwise.
//
//-----------------------------------------------------------------------------
BOOL
IsSageAware(CHAR * pszCmd, const CHAR * pszParams, int * pnSetNum)
{
    int index;
    HKEY hSageKey;
    HKEY hSageSubKey;
    CHAR szSubKey[MAXPATH];
    CHAR szSubKeyPath[MAXPATH];
    CHAR szRegValue[MAXCOMMANDLINE];
    long lRet;
    CHAR *p;
    BOOL fResult = FALSE;
    DWORD cb;

    if (pnSetNum != NULL)
    {
        *pnSetNum = 0;
    }

    if (RegOpenKey(HKEY_LOCAL_MACHINE, SAGE_KEY, &hSageKey))
    {
        return FALSE;
    }

#define MAXSAGEPROGS 0xffffff

    for (index = 0; index < MAXSAGEPROGS; index++)
    {
        //
        // Examine each subkey of the Sage key.
        //
        lRet = RegEnumKey(hSageKey, index, szSubKey, MAXPATH);

        if (lRet != ERROR_SUCCESS)
        {
            break;
        }

        lstrcpy(szSubKeyPath, SAGE_KEY);
        lstrcat(szSubKeyPath, "\\");
        lstrcat(szSubKeyPath, szSubKey);

        if (RegOpenKey(HKEY_LOCAL_MACHINE,
                       szSubKeyPath,
                       &hSageSubKey) != ERROR_SUCCESS)
        {
            continue; //no path
        }

        //
        // Look for a match on the application name.
        //
        cb = MAXPATH;
        if (RegQueryValueEx(hSageSubKey,
                            PROGRAM_VALUE,
                            NULL,
                            NULL,
                            (LPBYTE)szRegValue,
                            &cb) != ERROR_SUCCESS)
        {
            RegCloseKey(hSageSubKey);
            continue; //no path
        }

        schDebugOut((DEB_ITRACE,
                     "IsSageAware enum: pszCmd = %s, szRegValue = %s\n",
                     pszCmd, szRegValue));

        RegCloseKey(hSageSubKey);

        if (AppNamesMatch(pszCmd, szRegValue))
        {
            if (RegOpenKey(HKEY_LOCAL_MACHINE,
                           szSubKeyPath,
                           &hSageSubKey) != ERROR_SUCCESS)
            {
                continue; //no settings dialog
            }

            fResult = FALSE;

            if (RegQueryValueEx(hSageSubKey,
                                SETTINGS_VALUE,
                                NULL,
                                NULL,
                                (LPBYTE)szRegValue,
                                &cb) == ERROR_SUCCESS)
            {
                if (szRegValue[0] == 0)
                {
                    RegCloseKey(hSageSubKey);
                    break; // this means don't allow sage settings
                }
            }
            else
            {
                RegCloseKey(hSageSubKey);
                continue; //no settings registry key
            }

            // test for app that can't handle multiple instances
            //
            if (!fAppCantSupportMultipleInstances(pszCmd))
            {
                fResult = TRUE;
            }

            if (fResult && pnSetNum != NULL)
            {
                //
                // Extract the set number from the parameters, if requested.
                //
                p = _tcsstr(pszParams, SAGERUN_PARAM);

                if (p != NULL)
                {
                    AnsiToIntA(p + lstrlen(SAGERUN_PARAM), pnSetNum);
                }
                else 
                {
                    // 
                    // This job is for a sage-aware app which supports the 
                    // SAGERUN switch, but it lacks that switch in its 
                    // parameter property.  Since the caller wants to know
                    // what value to use, generate one.
                    //

                    HRESULT hr = GetNextSageRunParam(hSageSubKey, pnSetNum);

                    if (FAILED(hr))
                    {
                        fResult = FALSE;
                    }
                }
            }

            RegCloseKey(hSageSubKey);
            break;
        }
    }

    RegCloseKey(hSageKey);

    return fResult;
}



const CHAR SET_PREFIX[] = "Set";

//+--------------------------------------------------------------------------
//
//  Function:   CreateSageRunKey
//
//  Synopsis:   Create a registry key with string representation of 
//              value [uiKey] for app [szSageAwareExe].
//
//  Arguments:  [szSageAwareExe] - app for which to create key
//              [uiKey]          - numeric representation of key name
//
//  Returns:    S_OK or E_FAIL
//
//  History:    10-28-1997   DavidMun   Created
//
//---------------------------------------------------------------------------

HRESULT
CreateSageRunKey(
    LPCSTR szSageAwareExe,
    UINT uiKey)
{
    HRESULT hr = E_FAIL;  // init for failure
    int index;
    HKEY hSageKey;
    HKEY hSageSubKey;
    CHAR szSubKey[MAXPATH];
    CHAR szSubKeyPath[MAXPATH];
    CHAR szRegValue[MAXCOMMANDLINE];
    long lRet;
    DWORD cb;

    if (RegOpenKey(HKEY_LOCAL_MACHINE, SAGE_KEY, &hSageKey))
    {
        return hr;
    }

    for (index = 0; index < MAXSAGEPROGS; index++)
    {
        //
        // Examine each subkey of the Sage key.
        //
        lRet = RegEnumKey(hSageKey, index, szSubKey, MAXPATH);

        if (lRet != ERROR_SUCCESS)
        {
            break;
        }

        lstrcpy(szSubKeyPath, SAGE_KEY);
        lstrcat(szSubKeyPath, "\\");
        lstrcat(szSubKeyPath, szSubKey);

        if (RegOpenKey(HKEY_LOCAL_MACHINE,
                       szSubKeyPath,
                       &hSageSubKey) != ERROR_SUCCESS)
        {
            continue; //no path
        }

        //
        // Look for a match on the application name.
        //
        cb = MAXPATH;
        if (RegQueryValueEx(hSageSubKey,
                            PROGRAM_VALUE,
                            NULL,
                            NULL,
                            (LPBYTE)szRegValue,
                            &cb) != ERROR_SUCCESS)
        {
            RegCloseKey(hSageSubKey);
            continue; //no path
        }

        RegCloseKey(hSageSubKey);

        if (AppNamesMatch((LPSTR)szSageAwareExe, szRegValue))
        {
            if (RegOpenKey(HKEY_LOCAL_MACHINE,
                           szSubKeyPath,
                           &hSageSubKey) != ERROR_SUCCESS)
            {
                continue; //no settings dialog
            }

            CHAR szKeyName[MAX_PATH];
            HKEY hkNewKey = NULL;

            wsprintf(szKeyName, "%s%u", SET_PREFIX, uiKey);
            lRet = RegCreateKey(hSageSubKey, szKeyName, &hkNewKey);

            if (hkNewKey)
            {
                RegCloseKey(hkNewKey);
            }
            RegCloseKey(hSageSubKey);

            if (lRet == ERROR_SUCCESS)
            {
                hr = S_OK;
            }
            else 
            {
                schDebugOut((DEB_ERROR,
                             "CreateSageRunKey: RegCreateKey %uL",
                             lRet));
            }
            break;
        }
    }

    RegCloseKey(hSageKey);
    return hr;
}




//+--------------------------------------------------------------------------
//
//  Function:   GetNextSageRunParam
//
//  Synopsis:   Fill *[pnSetNum] with the next <n> value to use for a new
//              Set<n> subkey under [hSageAppSubKey].
//
//  Arguments:  [hSageAppSubKey] - handle to app's key under SAGE key
//              [pnSetNum]       - filled with next number to use
//
//  Returns:    HRESULT
//
//  Modifies:   *[pnSetNum]
//
//  History:    3-06-1997   DavidMun   Created
//
//---------------------------------------------------------------------------

HRESULT 
GetNextSageRunParam(
    HKEY hSageAppSubKey,
    PINT pnSetNum)
{
    HRESULT hr = S_OK;
    ULONG   idxKey;

    *pnSetNum = 0;

    for (idxKey = 0; TRUE; idxKey++)
    {
        LONG lr;
        CHAR szSubKey[MAX_PATH + 1];

        lr = RegEnumKey(hSageAppSubKey, idxKey, szSubKey, ARRAYLEN(szSubKey));

        //
        // Quit on error, including end of subkeys
        //

        if (lr != ERROR_SUCCESS)
        {
            if (lr != ERROR_NO_MORE_ITEMS)
            {
                hr = E_FAIL;
                schDebugOut((DEB_ERROR,
                             "GetNextSageRunParam: RegEnumKey %uL",
                             lr));
            }
            break;
        }

        //
        // Ignore this key if it doesn't start with "Set"
        //

        CHAR szSet[ARRAYLEN(SET_PREFIX)];
        lstrcpyn(szSet, szSubKey, ARRAYLEN(SET_PREFIX));

        if (lstrcmpi(szSet, SET_PREFIX))
        {
            continue;
        }

        //
        // Get the numeric value after the prefix.  If there's no valid
        // number, ignore this key.
        //

        BOOL fNumber;
        INT  iSetN;

        fNumber = AnsiToIntA(szSubKey + ARRAYLEN(SET_PREFIX) - 1, &iSetN);

        if (!fNumber)
        {
            continue;
        }

        //
        // If the number matches or exceeds the one we plan to use, make ours 
        // larger.
        //

        if (iSetN >= *pnSetNum)
        {
            *pnSetNum = iSetN + 1;
        }
    }
    return hr;
}




//+---------------------------------------------------------------------------
//
//  Function:   GetAppNameFromPath
//
//  Synopsis:   
//
//  Arguments:  [pszFullPathName] - full or partial path
//              [pszAppName]      - buffer for app name, may be NULL
//
//  Returns:    TRUE  - [pszFullPathName] contained slashes
//              FALSE -  [pszFullPathName] didn't contain slashes
//
//  Modifies:   *[pszAppName]
//
//  History:    10-25-96   DavidMun   Made DBCS safe
//
//----------------------------------------------------------------------------

BOOL GetAppNameFromPath(CHAR * pszFullPathName, CHAR * pszAppName)
{
    LPSTR pszLastSlash;

    pszLastSlash = _tcsrchr(pszFullPathName, '\\');

    if (!pszAppName)
    {
        return pszLastSlash != NULL;
    }

    if (pszLastSlash)
    {
        lstrcpy(pszAppName, pszLastSlash + 1);
    }
    else
    {
        lstrcpy(pszAppName, pszFullPathName);
    }

    schDebugOut((DEB_ITRACE, "GetAppNameFromPath app name: %s\n", pszAppName));

    if (pszAppName[0] != '"')
    {
        LPSTR pszQuote = _tcschr(pszAppName, '"');

        if (pszQuote)
        {
            *pszQuote = '\0';
        }
    }
    return pszLastSlash != NULL;
}

BOOL AppNamesMatch(CHAR *pszCommand1, CHAR *pszCommand2)
{
    CHAR short1[MAXPATH];
    CHAR short2[MAXPATH];

    GetAppNameFromPath(pszCommand1, short1);
    GetAppNameFromPath(pszCommand2, short2);

    if (lstrcmpi(short1, short2) == 0)
    {
        return(TRUE);
    }
    return(FALSE);
}

BOOL fAppCantSupportMultipleInstances(CHAR * pszCmd)
{
    if (AppNamesMatch(pszCmd, "SCANDSKW.EXE") ||
        AppNamesMatch(pszCmd, "DEFRAG.EXE")   ||
        AppNamesMatch(pszCmd, "CMPAGENT.EXE"))
    {
        if (FindWindow("ScanDskWDlgClass", NULL))
            return TRUE;
        if (FindWindow("MSDefragWClass1", NULL))
            return TRUE;
        if (FindWindow("MSExtraPakWClass1", NULL))
            return TRUE;
    }
    return FALSE;
}

/*----------------------------------------------------------
Purpose: ScottH's version of atoi.  Supports hexadecimal too.

         If this function returns FALSE, *piRet is set to 0.

Returns: TRUE if the string is a number, or contains a partial number
         FALSE if the string is not a number
*/
BOOL AnsiToIntA(LPCSTR pszString, int * piRet)
{
    #define InRange(id, idFirst, idLast) \
            ((UINT)(id-idFirst) <= (UINT)(idLast-idFirst))
    #define IS_DIGIT(ch)    InRange(ch, '0', '9')

    BOOL bRet;
    int n;
    BOOL bNeg = FALSE;
    LPCSTR psz;
    LPCSTR pszAdj;

    // Skip leading whitespace
    //
    for (psz = pszString;
         *psz == ' ' || *psz == '\n' || *psz == '\t';
         psz = NextChar(psz))
        ;

    // Determine possible explicit signage
    //
    if (*psz == '+' || *psz == '-')
        {
        bNeg = (*psz == '+') ? FALSE : TRUE;
        psz++;
        }

    // Or is this hexadecimal?
    //
    pszAdj = NextChar(psz);
    if (*psz == '0' && (*pszAdj == 'x' || *pszAdj == 'X'))
        {
        // Yes

        // (Never allow negative sign with hexadecimal numbers)
        bNeg = FALSE;
        psz = NextChar(pszAdj);

        pszAdj = psz;

        // Do the conversion
        //
        for (n = 0; ; psz = NextChar(psz))
            {
            if (IS_DIGIT(*psz))
                n = 0x10 * n + *psz - '0';
            else
                {
                CHAR ch = *psz;
                int n2;

                if (ch >= 'a')
                    ch -= 'a' - 'A';

                n2 = ch - 'A' + 0xA;
                if (n2 >= 0xA && n2 <= 0xF)
                    n = 0x10 * n + n2;
                else
                    break;
                }
            }

        // Return TRUE if there was at least one digit
        bRet = (psz != pszAdj);
        }
    else
        {
        // No
        pszAdj = psz;

        // Do the conversion
        for (n = 0; IS_DIGIT(*psz); psz = NextChar(psz))
            n = 10 * n + *psz - '0';

        // Return TRUE if there was at least one digit
        bRet = (psz != pszAdj);
        }

    *piRet = bNeg ? -n : n;

    return bRet;
}