471 lines
13 KiB
C++
471 lines
13 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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 <headers.hxx>
|
|
#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 ' ';
|
|
}
|
|
|