//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 1996. // // File: parse.cxx // // Contents: Functions that support parsing. // // History: 04-01-95 DavidMun Created // //---------------------------------------------------------------------------- #include #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; }