//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 1996. // // File: atsign.cxx // // Contents: Functions to read commands from a script file. // // History: 04-20-95 davidmun Created // //---------------------------------------------------------------------------- #include #pragma hdrstop #include "jt.hxx" // // Forward references // ULONG FindEol(CHAR *pstr, ULONG cchRemaining); CHAR FindNextNonSpace(CHAR *pstr, ULONG cchRemaining); //+--------------------------------------------------------------------------- // // Function: DoAtSign // // Synopsis: Get filename from commandline and process it. // // Arguments: [ppwsz] - command line // // Returns: S_OK - file processed without error // E_* - error logged // // Modifies: *[ppwsz] // // History: 04-20-95 davidmun Created // 01-03-96 DavidMun Support multi-line commands // // Notes: This routine may be indirectly recursive. // //---------------------------------------------------------------------------- HRESULT DoAtSign(WCHAR **ppwsz) { HRESULT hr = S_OK; ShHANDLE shFile; ShHANDLE shFileMapping; VOID *pvBase = NULL; BOOL fOk; ULONG cchFile; TCHAR tszFilename[MAX_PATH + 1] = TEXT("None"); g_Log.Write(LOG_DEBUG, "DoAtSign"); do { hr = GetFilename(ppwsz, L"command file name"); BREAK_ON_FAILURE(hr); #ifdef UNICODE wcscpy(tszFilename, g_wszLastStringToken); #else wcstombs(tszFilename, g_wszLastStringToken, wcslen(g_wszLastStringToken)+1); #endif // // Open the command file, then hand one line at a time to // ProcessCommandLine (which is our caller, so we're recursing). // shFile = CreateFile( tszFilename, GENERIC_READ, FILE_SHARE_READ, NULL, // default security OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); // no template if (shFile == INVALID_HANDLE_VALUE) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "Can't open file %S", tszFilename); break; } shFileMapping = CreateFileMapping( shFile, NULL, // default security PAGE_READONLY, 0, 0, // max size is size of file NULL); // unnamed object if (shFileMapping == NULL) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "CreateFileMapping (%u)", GetLastError()); break; } pvBase = MapViewOfFile( shFileMapping, FILE_MAP_READ, 0, 0, // start mapping at beginning of file 0); // map entire file if (!pvBase) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "MapViewOfFile (%u)", GetLastError()); break; } // // pvBase points to start of mapped file. Get the file length. // BY_HANDLE_FILE_INFORMATION bhfi; fOk = GetFileInformationByHandle(shFile, &bhfi); if (!fOk) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "GetFileInformationByHandle (%u)", GetLastError()); break; } cchFile = bhfi.nFileSizeLow; if (bhfi.nFileSizeHigh) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "File too large"); break; } } while (0); // // Finally ready to process file. // ULONG cchProcessed = 0; ULONG ulCurLine = 1; CHAR *pstrFile = (CHAR *) pvBase; while (SUCCEEDED(hr) && cchProcessed < cchFile) { CHAR szLine[MAX_TOKEN_LEN + 1] = ""; ULONG cchLine = 0; WCHAR wszLine[MAX_TOKEN_LEN + 1]; WCHAR wszExpandedLine[MAX_TOKEN_LEN + 1]; BOOL fInQuote = FALSE; BOOL fFoundNextCommand = FALSE; // // Copy all chars of the next command from pstrfile into wszline, // condensing whitespace into single blanks and skipping comment // lines. // for (; cchProcessed < cchFile && !fFoundNextCommand && cchLine < MAX_TOKEN_LEN; cchProcessed++) { // // If we're in a quoted string, copy everything verbatim until // closing quote // if (fInQuote) { if (pstrFile[cchProcessed] == '"') { fInQuote = FALSE; } // // Check for error case of newline in string // if (pstrFile[cchProcessed] == '\n') { hr = E_FAIL; g_Log.Write(LOG_FAIL, "Newline in string constant"); break; } szLine[cchLine++] = pstrFile[cchProcessed]; continue; } // // Not already in a quoted string. See if we're entering one. // if (pstrFile[cchProcessed] == '"') { fInQuote = TRUE; szLine[cchLine++] = pstrFile[cchProcessed]; continue; } // // Not in or starting a quoted string, so we're free to condense // whitespace (including newlines) and ignore comment lines // if (isspace(pstrFile[cchProcessed])) { // // Only copy this space char if none has been copied yet. // Bump the line count if the whitespace char we're skipping // is a newline. // if (cchLine && szLine[cchLine - 1] != ' ') { szLine[cchLine++] = ' '; } if (pstrFile[cchProcessed] == '\n') { ulCurLine++; } continue; } if (pstrFile[cchProcessed] == ';') { // // Skip to end of line // cchProcessed += FindEol( &pstrFile[cchProcessed], cchFile - cchProcessed); // subtract 1 because for loop is going to add one cchProcessed--; continue; } // // Next char is not quote, semicolon (start of comment), or // whitespace. If we haven't copied anything yet, copy that char // as the first of the line. // if (!cchLine) { szLine[cchLine++] = pstrFile[cchProcessed]; continue; } // // Since we've already started copying stuff, we want to quit when // we get to the start of the next command, i.e., when we see a // switch char '/' or '-'. // // Unfortunately these two characters also delimit the parts of a // date, and we don't want to stop copying the line because of a // date. // // Therefore we'll only stop copying if the next non whitespace // character is not a number. This imposes the constraints that // no commands can be a number (i.e. /10 cannot be a valid // command) and that dates must use only digits (i.e. 10-Feb is // not valid because we'll think -Feb is a command and only copy // the 10). // // Note it isn't safe to check that the *previous* character was a // digit and assume we're in a date, since a command with a // numeric argument could be mistaken for a date. // // /foo bar=10 /baz // if (pstrFile[cchProcessed] == '/' || pstrFile[cchProcessed] == '-') { CHAR ch; ch = FindNextNonSpace( &pstrFile[cchProcessed + 1], cchFile - (cchProcessed + 1)); if (isdigit(ch)) { szLine[cchLine++] = pstrFile[cchProcessed]; } else { fFoundNextCommand = TRUE; cchProcessed--; // because for loop will increment it } } else { szLine[cchLine++] = pstrFile[cchProcessed]; } } BREAK_ON_FAILURE(hr); // // If we stopped copying not because we found the next command or hit // the end of the file, then it's because we ran out of room in // szLine, which is an error. // if (!fFoundNextCommand && cchProcessed < cchFile) { hr = E_FAIL; g_Log.Write( LOG_ERROR, "Line %u is longer than %u chars", ulCurLine, MAX_TOKEN_LEN); break; } #ifdef UNICODE // // Convert line to wchar and null terminate it. // mbstowcs(wszLine, szLine, cchLine); wszLine[cchLine] = L'\0'; // // Expand environment variables // ULONG cchRequired; cchRequired = ExpandEnvironmentStrings( wszLine, wszExpandedLine, MAX_TOKEN_LEN+1); #else CHAR szExpandedLine[MAX_TOKEN_LEN + 1]; ULONG cchRequired; szLine[cchLine] = '\0'; cchRequired = ExpandEnvironmentStrings( szLine, szExpandedLine, MAX_TOKEN_LEN+1); mbstowcs(wszExpandedLine, szExpandedLine, MAX_TOKEN_LEN + 1); #endif if (!cchRequired || cchRequired > MAX_TOKEN_LEN + 1) { hr = E_FAIL; g_Log.Write(LOG_FAIL, "ExpandEnvironmentStrings failed"); break; } // // Perform the command in wszExpandedLine, then loop around and // read the next command. // if (SUCCEEDED(hr)) { g_Log.Write(LOG_DEBUG, "DoAtSign: processing '%S'", wszExpandedLine); hr = ProcessCommandLine(wszExpandedLine); } } if (FAILED(hr)) { g_Log.Write(LOG_ERROR, "File: %S Line: %u", tszFilename, ulCurLine); } if (pvBase) { fOk = UnmapViewOfFile(pvBase); if (!fOk) { hr = E_FAIL; g_Log.Write(LOG_ERROR, "UnmapViewOfFile (%u)", GetLastError()); } } return hr; } //+--------------------------------------------------------------------------- // // Function: FindEol // // Synopsis: Return the number of characters between [pstr] and the end // of the line. // // Arguments: [pstr] - non-terminated string // [cchRemaining] - characters till eof // // Returns: Character count // // History: 04-20-95 davidmun Created // //---------------------------------------------------------------------------- ULONG FindEol(CHAR *pstr, ULONG cchRemaining) { CHAR *pstrCur; ULONG cchCur; for (pstrCur = pstr, cchCur = 0; cchCur < cchRemaining; cchCur++, pstrCur++) { if (*pstrCur == '\r' || *pstrCur == '\n') { return cchCur; } } return cchCur; } //+--------------------------------------------------------------------------- // // Function: FindNextNonSpace // // Synopsis: Return the next non-whitespace character in [pstr], or space // if there are no non-whitespace characters in the next // [cchRemaining] characters. // // Arguments: [pstr] - non-terminated string // [cchRemaining] - number of characters in string // // Returns: Character as described. // // History: 01-10-96 DavidMun Created // //---------------------------------------------------------------------------- CHAR FindNextNonSpace(CHAR *pstr, ULONG cchRemaining) { CHAR *pstrCur; ULONG cchCur; for (pstrCur = pstr, cchCur = 0; cchCur < cchRemaining; cchCur++, pstrCur++) { if (!isspace(*pstrCur)) { return *pstrCur; } } return ' '; }