windows-nt/Source/XPSP1/NT/admin/services/sched/test/jt/parse.cxx
2020-09-26 16:20:57 +08:00

1521 lines
35 KiB
C++

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1994 - 1996.
//
// File: parse.cxx
//
// Contents: Functions that support parsing.
//
// History: 04-01-95 DavidMun Created
//
//----------------------------------------------------------------------------
#include <headers.hxx>
#pragma hdrstop
#include "jt.hxx"
//
// Private globals
//
// s_awszTokens - the strings in this array must exactly match the order
// of tokens in the TOKEN enum in parse.hxx.
//
WCHAR *s_awszTokens[] =
{
L"ABJ",
L"ABQ",
L"AJQ",
L"CSAGE",
L"CTJ",
L"CTQ",
L"DTJ",
L"DTQ",
L"EJ",
L"EJQ",
L"ENC",
L"ENN",
L"ENR",
L"ENS",
L"GC",
L"GM",
L"LJ",
L"LQ",
L"NSGA",
L"NSSA",
L"PJ",
L"PQ",
L"PRJ",
L"PRQ",
L"PSJ",
L"PSQ",
L"PTJ",
L"PTQ",
L"RJ",
L"RQ",
L"RMJQ",
L"SAC",
L"SAJ",
L"SAQ",
L"SC",
L"SCE",
L"SD",
L"SE",
L"ISJQ",
L"SJ",
L"SM",
L"SNJ",
L"SNQ",
L"SQ",
L"STJ",
L"STQ",
L"SVJ",
L"SVQ",
L"ApplicationName",
L"Parameters",
L"WorkingDirectory",
L"Comment",
L"Creator",
L"Priority",
L"MaxRunTime",
L"TaskFlags",
L"Interactive",
L"DeleteWhenDone",
L"Suspend",
L"NetSchedule",
L"DontStartIfOnBatteries",
L"KillIfGoingOnBatteries",
L"RunOnlyIfLoggedOn",
L"Hidden",
L"StartDate",
L"EndDate",
L"StartTime",
L"MinutesDuration",
L"HasEndDate",
L"KillAtDuration",
L"StartOnlyIfIdle",
L"KillOnIdleEnd",
L"RestartOnIdleResume",
L"SystemRequired",
L"Disabled",
L"MinutesInterval",
L"Type",
L"TypeArguments",
L"IDLE",
L"NORMAL",
L"HIGH",
L"REALTIME",
L"ONCE",
L"DAILY",
L"WEEKLY",
L"MONTHLYDATE",
L"MONTHLYDOW",
L"YEARLYDATE",
L"YEARLYDOW",
L"ONIDLE",
L"ATSTARTUP",
L"ATLOGON",
//
// CAUTION: single-character nonalpha tokens need to be added to the
// constant DELIMITERS.
//
L"TODAY",
L"NOW",
L"=",
L"@",
L"?",
L":",
L",",
L"!"
};
const WCHAR DELIMITERS[] = L"=@?:,!;/- \t";
#define NUM_TOKEN_STRINGS ARRAY_LEN(s_awszTokens)
//
// Forward references
//
WCHAR *SkipSpaces(WCHAR *pwsz);
TOKEN _GetStringToken(WCHAR **ppwsz);
TOKEN _GetNumberToken(WCHAR **ppwsz, WCHAR *pwszEnd);
//+---------------------------------------------------------------------------
//
// Function: ProcessCommandLine
//
// Synopsis: Dispatch to the routine that completes parsing for and carries
// out the next command specified on [pwszCommandLine].
//
// Arguments: [pwszCommandLine] - command line
//
// Returns: S_OK - command performed
// E_* - error logged
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
HRESULT ProcessCommandLine(WCHAR *pwszCommandLine)
{
HRESULT hr;
WCHAR *pwsz = pwszCommandLine;
static CHAR s_szJob[] = "Job";
static CHAR s_szQueue[] = "Queue";
//
// If a command starts with TKN_BANG instead of TKN_SWITCH, then its
// return value will be ignored. Clear this flag in any case statement
// that should cause a failure regardless of the use of TKN_BANG (see
// TKN_INVALID, for example).
//
BOOL fIgnoreReturnValue = FALSE;
while (*pwsz)
{
WCHAR *pwszLast = pwsz;
TOKEN tkn;
hr = S_OK;
tkn = GetToken(&pwsz);
if (tkn == TKN_EOL)
{
break;
}
if (tkn == TKN_SWITCH)
{
tkn = GetToken(&pwsz);
}
else if (tkn == TKN_BANG)
{
fIgnoreReturnValue = TRUE;
tkn = GetToken(&pwsz);
}
else if (tkn != TKN_ATSIGN && tkn != TKN_QUESTION)
{
hr = E_FAIL;
LogSyntaxError(tkn, L"'/', '-', '?', '!', or '@'");
break;
}
switch (tkn)
{
#if 0
case TKN_ABORTQUEUE:
hr = Abort(&pwsz, FALSE);
break;
case TKN_LOADQUEUE:
hr = Load(&pwsz, s_szQueue, FALSE);
break;
case TKN_CREATETRIGGERQUEUE:
hr = CreateTrigger(&pwsz, FALSE);
break;
case TKN_DELETETRIGGERQUEUE:
hr = DeleteTrigger(&pwsz, FALSE);
break;
case TKN_EDITJOBINQUEUE:
hr = EditJob(&pwsz, FALSE);
break;
case TKN_PRINTQUEUE:
hr = PrintAll(&pwsz, FALSE);
break;
case TKN_PRINTRUNTIMEQUEUE:
hr = PrintRunTimes(&pwsz, FALSE);
break;
case TKN_PRINTTRIGGERQUEUE:
hr = PrintTrigger(&pwsz, FALSE);
break;
case TKN_PRINTSTRINGQUEUE:
hr = PrintTriggerStrings(&pwsz, s_szQueue, FALSE);
break;
case TKN_RUNQUEUE:
hr = Run(FALSE);
break;
case TKN_SAVEQUEUE:
hr = Save(&pwsz, s_szQueue, FALSE);
break;
case TKN_SETTRIGGERQUEUE:
hr = SetTrigger(&pwsz, FALSE);
break;
#endif
case TKN_ATSIGN:
hr = DoAtSign(&pwsz);
break;
case TKN_ABORTJOB:
hr = Abort(&pwsz, TRUE);
break;
#ifndef RES_KIT
case TKN_CONVERTSAGETASKSTOJOBS:
hr = ConvertSage();
break;
#endif // RES_KIT not defined
case TKN_CREATETRIGGERJOB:
hr = CreateTrigger(&pwsz, TRUE);
break;
case TKN_DELETETRIGGERJOB:
hr = DeleteTrigger(&pwsz, TRUE);
break;
#ifndef RES_KIT
case TKN_EDITJOB:
hr = EditJob(&pwsz, TRUE);
break;
case TKN_ENUMCLONE:
hr = EnumClone(&pwsz);
break;
case TKN_ENUMNEXT:
hr = EnumNext(&pwsz);
break;
case TKN_ENUMRESET:
hr = EnumReset(&pwsz);
break;
case TKN_ENUMSKIP:
hr = EnumSkip(&pwsz);
break;
#endif // RES_KIT not defined
case TKN_EOL:
hr = E_FAIL;
fIgnoreReturnValue = FALSE; // be sure we exit loop
g_Log.Write(LOG_ERROR, "Unexpected end of line after switch character");
break;
case TKN_GETCREDENTIALS:
hr = GetCredentials();
break;
case TKN_GETMACHINE:
hr = SchedGetMachine();
break;
case TKN_INVALID:
hr = E_FAIL;
fIgnoreReturnValue = FALSE; // be sure we exit loop
LogSyntaxError(tkn, L"valid token after switch");
break;
case TKN_LOADJOB:
hr = Load(&pwsz, s_szJob, TRUE);
break;
case TKN_SETCREDENTIALS:
hr = SetCredentials(&pwsz);
break;
case TKN_SETMACHINE:
hr = SchedSetMachine(&pwsz);
break;
case TKN_PRINTJOB:
hr = PrintAll(&pwsz, TRUE);
break;
case TKN_PRINTRUNTIMEJOB:
hr = PrintRunTimes(&pwsz, TRUE);
break;
case TKN_PRINTTRIGGERJOB:
hr = PrintTrigger(&pwsz, TRUE);
break;
case TKN_PRINTSTRINGJOB:
hr = PrintTriggerStrings(&pwsz, s_szJob, TRUE);
break;
case TKN_NSGETACCOUNTINFO:
hr = PrintNSAccountInfo();
break;
case TKN_NSSETACCOUNTINFO:
hr = SetNSAccountInfo(&pwsz);
break;
case TKN_QUESTION:
DoHelp(&pwsz);
break;
case TKN_RUNJOB:
hr = Run(TRUE);
break;
case TKN_SAVEJOB:
hr = Save(&pwsz, s_szJob, TRUE);
break;
case TKN_SCHEDADDJOB:
hr = SchedAddJob(&pwsz);
break;
case TKN_SCHEDACTIVATE:
hr = SchedActivate(&pwsz);
break;
#ifndef RES_KIT
case TKN_SCHEDCREATEENUM:
hr = SchedCreateEnum(&pwsz);
break;
#endif // RES_KIT not defined
case TKN_SCHEDDELETE:
hr = SchedDelete(&pwsz);
break;
case TKN_SCHEDENUM:
hr = SchedEnum(&pwsz);
break;
#ifndef RES_KIT
case TKN_SCHEDISJOBORQUEUE:
hr = SchedIsJobOrQueue(&pwsz);
break;
#endif // RES_KIT not defined
case TKN_SCHEDNEWJOB:
hr = SchedNewJob(&pwsz);
break;
case TKN_SETJOB:
hr = SetJob(&pwsz);
break;
case TKN_SETTRIGGERJOB:
hr = SetTrigger(&pwsz, TRUE);
break;
default:
hr = E_FAIL;
fIgnoreReturnValue = FALSE; // be sure we exit loop
LogSyntaxError(tkn, L"command");
break;
}
if (fIgnoreReturnValue)
{
fIgnoreReturnValue = FALSE;
}
else if (FAILED(hr))
{
break;
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: LogSyntaxError
//
// Synopsis: Complain that we expected [pwszExpected] but found [tkn].
//
// Arguments: [tkn] - token that was found
// [pwszExpected] - description of what was expected
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
VOID LogSyntaxError(TOKEN tkn, WCHAR *pwszExpected)
{
if (tkn == TKN_INVALID)
{
return;
}
if (tkn == TKN_EOL)
{
g_Log.Write(
LOG_ERROR,
"Expected %S but found end of line",
pwszExpected);
}
else
{
g_Log.Write(
LOG_ERROR,
"Expected %S but found token '%S'",
pwszExpected,
GetTokenStringForLogging(tkn));
}
}
//+---------------------------------------------------------------------------
//
// Function: GetToken
//
// Synopsis: Return a token representing the next characters in *[ppwsz],
// and advance *[ppwsz] past the end of this token.
//
// Arguments: [ppwsz] - command line
//
// Returns: token describing characters found
//
// Modifies: *[ppwsz]
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
TOKEN GetToken(WCHAR **ppwsz)
{
ULONG i;
*ppwsz = SkipSpaces(*ppwsz);
if (!**ppwsz)
{
return TKN_EOL;
}
if (**ppwsz == L';')
{
*ppwsz += wcslen(*ppwsz);
return TKN_EOL;
}
if (**ppwsz == L'/' || **ppwsz == L'-')
{
++*ppwsz;
return TKN_SWITCH;
}
if (**ppwsz == L'"')
{
return _GetStringToken(ppwsz);
}
if (iswdigit(**ppwsz))
{
WCHAR *pwszEnd;
ULONG ulTemp;
ulTemp = wcstoul(*ppwsz, &pwszEnd, 10);
g_ulLastNumberToken = ulTemp;
return _GetNumberToken(ppwsz, pwszEnd);
}
ULONG cchToken;
//
// We've already skipped leading whitespace, so length of token is number
// of characters that are not whitespace or single-character tokens. If
// wcscspn returns 0, then **ppwsz must be one of the single character
// tokens.
//
cchToken = wcscspn(*ppwsz, DELIMITERS);
if (!cchToken)
{
cchToken = 1;
}
//
// Check the input against all the tokens
//
for (i = 0; i < NUM_TOKEN_STRINGS; i++)
{
if (!_wcsnicmp(*ppwsz, s_awszTokens[i], cchToken))
{
if (wcslen(s_awszTokens[i]) != cchToken)
{
continue;
}
*ppwsz += cchToken;
return (TOKEN) i;
}
}
//
// Not a number or token. Return it as a string.
//
return _GetStringToken(ppwsz);
}
//+---------------------------------------------------------------------------
//
// Function: _GetStringToken
//
// Synopsis: Treat *[ppwsz] as the start of an optionally quote-enclosed
// string (even if it's a digit or matches a predefined token).
//
// Arguments: [ppwsz] - command line
//
// Returns: TKN_STRING
// TKN_INVALID - string too long
//
// Modifies: Moves *[ppwsz] past end of string; g_wszLastStringToken.
//
// History: 04-21-95 DavidMun Created
// 01-08-96 DavidMun Allow empty strings
//
//----------------------------------------------------------------------------
TOKEN _GetStringToken(WCHAR **ppwsz)
{
BOOL fFoundQuote = FALSE;
*ppwsz = SkipSpaces(*ppwsz);
if (!**ppwsz)
{
return TKN_EOL;
}
if (**ppwsz == L';')
{
*ppwsz += wcslen(*ppwsz);
return TKN_EOL;
}
if (**ppwsz == L'"')
{
++*ppwsz;
fFoundQuote = TRUE;
}
//
// It's not a recognized token, so consider it a string. If we found a
// double-quote, copy everything till next double quote into
// g_wszLastStringToken. If not, just copy till next whitespace char or
// eol.
//
// Note that if !fFoundQuote *ppwsz != L'\0' or whitespace or else we
// would've returned TKN_EOL already.
//
ULONG cchToCopy;
if (fFoundQuote)
{
if (**ppwsz == L'"')
{
++*ppwsz;
g_wszLastStringToken[0] = L'\0';
return TKN_STRING;
}
if (!**ppwsz)
{
g_Log.Write(LOG_ERROR, "Syntax: '\"' followed by end of line");
return TKN_INVALID;
}
cchToCopy = wcscspn(*ppwsz, L"\"");
if ((*ppwsz)[cchToCopy] != L'"')
{
*ppwsz += cchToCopy;
g_Log.Write(LOG_ERROR, "Syntax: unterminated string");
return TKN_INVALID;
}
}
else
{
cchToCopy = wcscspn(*ppwsz, L", \t");
}
if (cchToCopy + 1 >= ARRAY_LEN(g_wszLastStringToken))
{
*ppwsz += cchToCopy + (fFoundQuote == TRUE);
g_Log.Write(
LOG_ERROR,
"String token > %u characters",
ARRAY_LEN(g_wszLastStringToken) - 1);
return TKN_INVALID;
}
wcsncpy(g_wszLastStringToken, *ppwsz, cchToCopy);
g_wszLastStringToken[cchToCopy] = L'\0';
*ppwsz += cchToCopy + (fFoundQuote == TRUE);
return TKN_STRING;
}
//+---------------------------------------------------------------------------
//
// Function: _GetNumberToken
//
// Synopsis: Copy the number starting at *[ppwsz] and ending at [pwszEnd]
// into g_wszLastNumberToken.
//
// Arguments: [ppwsz] - command line containing number
// [pwszEnd] - first non-numeric character in command line
//
// Returns: TKN_NUMBER - g_wszLastNumberToken modified
// TKN_INVALID - string too long, g_wszLastNumberToken unchanged
//
// Modifies: *[ppwsz] is always moved to [pwszEnd].
// g_wszLastNumberToken gets copy of number, but only if
// return value is TKN_NUMBER.
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
TOKEN _GetNumberToken(WCHAR **ppwsz, WCHAR *pwszEnd)
{
ULONG cchToCopy;
cchToCopy = pwszEnd - *ppwsz;
if (cchToCopy >= ARRAY_LEN(g_wszLastNumberToken))
{
*ppwsz = pwszEnd;
g_Log.Write(
LOG_ERROR,
"Number token > %u characters",
ARRAY_LEN(g_wszLastNumberToken) - 1);
return TKN_INVALID;
}
wcsncpy(g_wszLastNumberToken, *ppwsz, cchToCopy);
g_wszLastNumberToken[cchToCopy] = L'\0';
*ppwsz = pwszEnd;
return TKN_NUMBER;
}
//+---------------------------------------------------------------------------
//
// Function: PeekToken
//
// Synopsis: Same as GetToken(), but *[ppwsz] is unmodified.
//
// Arguments: [ppwsz] - command line
//
// Returns: token describing characters at *[ppwsz]
//
// Modifies: May modify g_*LastNumberToken, g_wszLastStringToken
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
TOKEN PeekToken(WCHAR **ppwsz)
{
WCHAR *pwszSavedPosition = *ppwsz;
TOKEN tkn;
tkn = GetToken(ppwsz);
*ppwsz = pwszSavedPosition;
return tkn;
}
//+---------------------------------------------------------------------------
//
// Function: Expect
//
// Synopsis: Get a token and log a syntax error if it isn't [tknExpected]
//
// Arguments: [tknExpected] - token we should get
// [ppwsz] - command line
// [wszExpected] - description for logging if next token isn't
// [tknExpected].
//
// Returns: S_OK - got expected token
// E_FAIL - got different token
//
// Modifies: *[ppwsz]
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
HRESULT Expect(TOKEN tknExpected, WCHAR **ppwsz, WCHAR *wszExpected)
{
HRESULT hr = S_OK;
TOKEN tkn;
if (tknExpected == TKN_STRING)
{
tkn = _GetStringToken(ppwsz);
}
else
{
tkn = GetToken(ppwsz);
}
if (tkn != tknExpected)
{
hr = E_FAIL;
LogSyntaxError(tkn, wszExpected);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: GetFilename
//
// Synopsis: Expect a string token at *[ppwsz] and convert it to a full
// path in g_wszLastStringToken.
//
// Arguments: [ppwsz] - command line
// [wszExpected] - for logging if next token isn't string
//
// Returns: S_OK - [wszExpected] valid
//
// Modifies: *[ppwsz], g_wszLastStringToken
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
HRESULT GetFilename(WCHAR **ppwsz, WCHAR *wszExpected)
{
HRESULT hr = S_OK;
WCHAR wszFullPath[MAX_PATH+1];
ULONG cchRequired;
TOKEN tkn;
do
{
tkn = _GetStringToken(ppwsz);
if (tkn != TKN_STRING)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Expected %S but got invalid or missing string",
wszExpected);
break;
}
#ifdef UNICODE
cchRequired = GetFullPathName(
g_wszLastStringToken,
MAX_PATH + 1,
wszFullPath,
NULL);
#else
CHAR szToken[MAX_PATH + 1];
CHAR szFullPath[MAX_PATH + 1];
wcstombs(szToken, g_wszLastStringToken, MAX_PATH + 1);
cchRequired = GetFullPathName(
szToken,
MAX_PATH + 1,
szFullPath,
NULL);
#endif
if (!cchRequired)
{
hr = E_FAIL;
g_Log.Write(
LOG_ERROR,
"GetFullPathName(%S) %u",
g_wszLastStringToken,
GetLastError());
break;
}
if (cchRequired > MAX_PATH)
{
hr = E_FAIL;
g_Log.Write(LOG_ERROR, "Full path > MAX_PATH chars");
break;
}
#ifdef UNICODE
wcscpy(g_wszLastStringToken, wszFullPath);
#else
mbstowcs(g_wszLastStringToken, szFullPath, MAX_PATH + 1);
#endif
} while (0);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ParseDate
//
// Synopsis: Fill [pwMonth], [pwDay], and [pwYear] with numeric values
// taken from the command line in [ppwsz].
//
// Arguments: [ppwsz] - Command line
// [pwMonth] - filled with first value
// [pwDay] - filled with second value
// [pwYear] - filled with third value
//
// Returns: S_OK - [pwMonth], [pwDay], and [pwYear] contain numbers
// (but do not necessarily constitute a valid date)
// E_* - error logged
//
// Modifies: All args.
//
// History: 01-04-96 DavidMun Created
//
// Notes: Dates can be of the form:
//
// n/n/n
// n-n-n
//
// or any other single character nonalpha token may be used to
// separate the numbers. If spaces appear on both sides of
// the tokens separating the numbers, then the tokens can be
// of any type at all.
//
//----------------------------------------------------------------------------
HRESULT ParseDate(WCHAR **ppwsz, WORD *pwMonth, WORD *pwDay, WORD *pwYear)
{
HRESULT hr = S_OK;
TOKEN tkn;
SYSTEMTIME stNow;
do
{
tkn = PeekToken(ppwsz);
if (tkn == TKN_TODAY)
{
GetToken(ppwsz);
GetLocalTime(&stNow);
*pwMonth = stNow.wMonth;
*pwDay = stNow.wDay;
*pwYear = stNow.wYear;
break;
}
hr = Expect(TKN_NUMBER, ppwsz, L"month value");
BREAK_ON_FAILURE(hr);
*pwMonth = (WORD) g_ulLastNumberToken;
GetToken(ppwsz); // eat whatever separator there is
hr = Expect(TKN_NUMBER, ppwsz, L"day value");
BREAK_ON_FAILURE(hr);
*pwDay = (WORD) g_ulLastNumberToken;
GetToken(ppwsz); // eat whatever separator there is
hr = Expect(TKN_NUMBER, ppwsz, L"year value");
BREAK_ON_FAILURE(hr);
*pwYear = (WORD) g_ulLastNumberToken;
if (*pwYear < 100)
{
*pwYear += 1900;
}
}
while (0);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ParseTime
//
// Synopsis: Fill [pwHour] and [pwMinute] with numeric values taken from
// the command line in [ppwsz].
//
// Arguments: [ppwsz] - command line
// [pwHour] - filled with first number
// [pwMinute] - filled with second number
//
// Returns: S_OK - [pwHour] and [pwMinute] contain numbers (but do not
// necessarily constitute a valid time).
// E_* - error logged
//
// Modifies: All args.
//
// History: 01-04-96 DavidMun Created
//
// Notes: See ParseDate for rules about delimiters.
//
//----------------------------------------------------------------------------
HRESULT ParseTime(WCHAR **ppwsz, WORD *pwHour, WORD *pwMinute)
{
HRESULT hr = S_OK;
TOKEN tkn;
SYSTEMTIME stNow;
do
{
tkn = PeekToken(ppwsz);
if (tkn == TKN_NOW)
{
GetToken(ppwsz);
GetLocalTime(&stNow);
//
// Add some time to the current time so that a trigger with
// NOW start time is far enough in the future to get run
//
AddSeconds(&stNow, TIME_NOW_INCREMENT);
*pwHour = stNow.wHour;
*pwMinute = stNow.wMinute;
break;
}
hr = Expect(TKN_NUMBER, ppwsz, L"hour value");
BREAK_ON_FAILURE(hr);
*pwHour = (WORD) g_ulLastNumberToken;
GetToken(ppwsz); // eat whatever separator there is
hr = Expect(TKN_NUMBER, ppwsz, L"minute value");
*pwMinute = (WORD) g_ulLastNumberToken;
}
while (0);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ParseDaysOfWeek
//
// Synopsis: Fill [pwDaysOfTheWeek] with the days of the week specified
// by the next string token in the command line.
//
// Arguments: [ppwsz] - command line
// [pwDaysOfTheWeek] - filled with JOB_*DAY bits
//
// Returns: S_OK - *[pwDaysOfTheWeek] valid
// E_* - invalid string token
//
// Modifies: All args.
//
// History: 01-04-96 DavidMun Created
//
//----------------------------------------------------------------------------
HRESULT ParseDaysOfWeek(WCHAR **ppwsz, WORD *pwDaysOfTheWeek)
{
HRESULT hr = S_OK;
TOKEN tkn;
WCHAR *pwszDay;
tkn = _GetStringToken(ppwsz);
if (tkn != TKN_STRING)
{
hr = E_FAIL;
}
*pwDaysOfTheWeek = 0;
for (pwszDay = g_wszLastStringToken; SUCCEEDED(hr) && *pwszDay; pwszDay++)
{
switch (towupper(*pwszDay))
{
case L'U':
*pwDaysOfTheWeek |= TASK_SUNDAY;
break;
case L'M':
*pwDaysOfTheWeek |= TASK_MONDAY;
break;
case L'T':
*pwDaysOfTheWeek |= TASK_TUESDAY;
break;
case L'W':
*pwDaysOfTheWeek |= TASK_WEDNESDAY;
break;
case L'R':
*pwDaysOfTheWeek |= TASK_THURSDAY;
break;
case L'F':
*pwDaysOfTheWeek |= TASK_FRIDAY;
break;
case L'A':
*pwDaysOfTheWeek |= TASK_SATURDAY;
break;
case L'.':
// ignore this, since we display day as . when its bit is off
break;
default:
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Expected day of week character 'UMTWRFA' but got '%wc'",
*pwszDay);
break;
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ParseDaysOfMonth
//
// Synopsis: Translate a comma separated list of day numbers into a bit
// field in [pdwDays].
//
// Arguments: [ppwsz] - command line
// [pdwDays] - least significant bit represents day 1
//
// Returns: S_OK
// E_FAIL - syntax or value error
//
// History: 03-07-96 DavidMun Created
//
// Notes: Day list may contain dashes to indicate day ranges. For
// example, "1,3-5,7,10-12" is equivalent to
// "1,3,4,5,7,10,11,12". Expressions like "1-3-5" are allowed
// (it's equivalent to "1-5"). Ranges with the first bit
// less than the second are automatically swapped, for example
// "4-2" is treated as "2-4". So even "4-2-1" would be
// interpreted as "1-4".
//
// Day numbers up to 32 are allowed for the purposes of
// exercising the error checking code in the job scheduler
// interfaces.
//
// CAUTION: this function will eat a trailing comma OR SWITCH
// character! It is therefore a requirement that a DaysOfMonth
// list be followed by some other nonswitch string to avoid
// having it eat the switch character that starts the next
// command.
//
//----------------------------------------------------------------------------
HRESULT ParseDaysOfMonth(WCHAR **ppwsz, DWORD *pdwDays)
{
HRESULT hr = S_OK;
TOKEN tkn;
ULONG ulLastDay = 0;
BOOL fGotDash = FALSE;
ULONG i;
*pdwDays = 0;
do
{
tkn = PeekToken(ppwsz);
//
// A string, EOL, or error token means we've gone past the end of the
// list of days and can quit. (The latter means we're about to
// abort!)
//
if (tkn == TKN_STRING || tkn == TKN_EOL || tkn == TKN_INVALID)
{
break;
}
//
// Eat commas, but don't allow "-,". Also, getting a comma resets the
// last day to zero, which allows TKN_SWITCH check to complain about
// "1,-2"
//
if (tkn == TKN_COMMA)
{
ulLastDay = 0;
if (fGotDash)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Expected a number following the dash but got ',' in day of month list");
break;
}
GetToken(ppwsz);
continue;
}
//
// A dash is valid only if the preceding token was a number
//
if (tkn == TKN_SWITCH)
{
if (fGotDash)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Didn't expect two switch characters in a row in day of month list");
break;
}
if (ulLastDay == 0)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Expected a number preceding switch character in day of month list");
break;
}
//
// It's ok to have a dash. Note that we got one, consume the
// token, and look at the next one.
//
fGotDash = TRUE;
GetToken(ppwsz);
continue;
}
//
// At this point, anything other than a number means we're done. If
// there's a hanging switch, though, that's an error.
if (tkn != TKN_NUMBER)
{
if (fGotDash)
{
hr = E_FAIL;
LogSyntaxError(tkn, L"a number following the switch character");
}
break;
}
//
// The next token is TKN_NUMBER, so consume it. Also make sure it's
// >= 1 and <= 32. Yes, 32, because we want to allow specifying an
// invalid bit pattern to the Job Scheduler code.
//
// If fGotDash, this number is the end of a range that started with
// ulLastDay (which has already been verified).
//
// Otherwise it's just a single day bit to turn on.
//
GetToken(ppwsz);
if (g_ulLastNumberToken < 1 ||
#ifndef RES_KIT
g_ulLastNumberToken > 32)
#else
g_ulLastNumberToken > 31)
#endif
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
#ifndef RES_KIT
"Expected a day number from 1 to 32 (yes, thirty-two) but got %u",
#else
"Day numbers run from 1 to 31, but %u was passed in, instead.",
#endif
g_ulLastNumberToken);
break;
}
if (fGotDash)
{
fGotDash = FALSE;
// allow backwards ranges
if (ulLastDay > g_ulLastNumberToken)
{
ULONG ulTemp = ulLastDay;
ulLastDay = g_ulLastNumberToken;
g_ulLastNumberToken = ulTemp;
}
//
// Turn on all the bits in the range. Note that the previous
// iteration already saw ulLastDay and turned it on, so we can
// skip that bit.
//
for (i = ulLastDay + 1; i <= g_ulLastNumberToken; i++)
{
*pdwDays |= 1 << (i - 1);
}
}
else
{
*pdwDays |= 1 << (g_ulLastNumberToken - 1);
}
ulLastDay = g_ulLastNumberToken;
} while (TRUE);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ParseMonths
//
// Synopsis: Translate MONTH_ABBREV_LEN character long month
// abbreviations in [ppwsz] into month bits in [pwMonths].
//
// Arguments: [ppwsz] - command line
// [pwMonths] - filled with month bits
//
// Returns: S_OK
// E_FAIL
//
// History: 03-07-96 DavidMun Created
//
//----------------------------------------------------------------------------
HRESULT ParseMonths(WCHAR **ppwsz, WORD *pwMonths)
{
HRESULT hr = S_OK;
TOKEN tkn;
WCHAR *pwszMonth;
ULONG i;
*pwMonths = 0;
tkn = _GetStringToken(ppwsz);
if (tkn != TKN_STRING)
{
hr = E_FAIL;
}
else if (wcslen(g_wszLastStringToken) % MONTH_ABBREV_LEN)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Month string must consist of %u letter abbreviations",
MONTH_ABBREV_LEN);
}
for (pwszMonth = g_wszLastStringToken;
SUCCEEDED(hr) && *pwszMonth;
pwszMonth += 3)
{
for (i = 0; i < 12; i++)
{
if (!_wcsnicmp(pwszMonth, g_awszMonthAbbrev[i], MONTH_ABBREV_LEN))
{
switch (i)
{
case 0:
*pwMonths |= TASK_JANUARY;
break;
case 1:
*pwMonths |= TASK_FEBRUARY;
break;
case 2:
*pwMonths |= TASK_MARCH;
break;
case 3:
*pwMonths |= TASK_APRIL;
break;
case 4:
*pwMonths |= TASK_MAY;
break;
case 5:
*pwMonths |= TASK_JUNE;
break;
case 6:
*pwMonths |= TASK_JULY;
break;
case 7:
*pwMonths |= TASK_AUGUST;
break;
case 8:
*pwMonths |= TASK_SEPTEMBER;
break;
case 9:
*pwMonths |= TASK_OCTOBER;
break;
case 10:
*pwMonths |= TASK_NOVEMBER;
break;
case 11:
*pwMonths |= TASK_DECEMBER;
break;
}
//
// Since we've found the month abbreviation, break out of the
// inner loop that's comparing abbreviations against the
// user's string.
//
break;
}
}
//
// If the inner loop found the next MONTH_ABBREV_LEN chars of the
// user's string in the g_awszMonthAbbrev array, then it executed the
// break and i would be less than 12.
//
if (i >= 12)
{
hr = E_FAIL;
g_Log.Write(
LOG_FAIL,
"Expected %u character month abbreviation at '%S",
MONTH_ABBREV_LEN,
pwszMonth);
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: GetTokenStringForLogging
//
// Synopsis: Return a human-readable string describing [tkn].
//
// Arguments: [tkn] - token to describe.
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
WCHAR *GetTokenStringForLogging(TOKEN tkn)
{
switch (tkn)
{
case TKN_INVALID:
return L"an invalid token";
case TKN_EOL:
return L"end of line";
case TKN_SWITCH:
return L"switch character";
case TKN_STRING:
return g_wszLastStringToken;
case TKN_NUMBER:
return g_wszLastNumberToken;
default:
if (tkn < NUM_TOKEN_STRINGS)
{
return s_awszTokens[tkn];
}
return L"an unknown token value";
}
}
//+---------------------------------------------------------------------------
//
// Function: SkipSpaces
//
// Synopsis: Return [pwsz] advanced to end of string, end of line, or
// next non-space character.
//
// Arguments: [pwsz] - string to skip spaces in
//
// Returns: [pwsz] + n
//
// History: 04-21-95 DavidMun Created
//
//----------------------------------------------------------------------------
WCHAR *SkipSpaces(WCHAR *pwsz)
{
while (*pwsz && *pwsz == L' ' || *pwsz == L'\t')
{
pwsz++;
}
return pwsz;
}