1150 lines
31 KiB
C
1150 lines
31 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1999 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
fileutil.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Implements utility routines for files, file paths, etc.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Jim Schmidt (jimschm) 08-Mar-2000
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
<alias> <date> <comments>
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "pch.h"
|
||
|
|
||
|
//
|
||
|
// Includes
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
#define DBG_FILEUTIL "FileUtil"
|
||
|
|
||
|
//
|
||
|
// Strings
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Constants
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Macros
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Types
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Globals
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Macro expansion list
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Private function prototypes
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Macro expansion definition
|
||
|
//
|
||
|
|
||
|
// None
|
||
|
|
||
|
//
|
||
|
// Code
|
||
|
//
|
||
|
|
||
|
BOOL
|
||
|
pDefaultFindFileA (
|
||
|
IN PCSTR FileName
|
||
|
)
|
||
|
{
|
||
|
return (GetFileAttributesA (FileName) != INVALID_ATTRIBUTES);
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
pDefaultFindFileW (
|
||
|
IN PCWSTR FileName
|
||
|
)
|
||
|
{
|
||
|
return (GetFileAttributesW (FileName) != INVALID_ATTRIBUTES);
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
pDefaultSearchPathA (
|
||
|
IN PCSTR FileName,
|
||
|
IN DWORD BufferLength,
|
||
|
OUT PSTR Buffer
|
||
|
)
|
||
|
{
|
||
|
PSTR dontCare;
|
||
|
return SearchPathA (NULL, FileName, NULL, BufferLength, Buffer, &dontCare);
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
pDefaultSearchPathW (
|
||
|
IN PCWSTR FileName,
|
||
|
IN DWORD BufferLength,
|
||
|
OUT PWSTR Buffer
|
||
|
)
|
||
|
{
|
||
|
PWSTR dontCare;
|
||
|
return SearchPathW (NULL, FileName, NULL, BufferLength, Buffer, &dontCare);
|
||
|
}
|
||
|
|
||
|
PCMDLINEA
|
||
|
ParseCmdLineExA (
|
||
|
IN PCSTR CmdLine,
|
||
|
IN PCSTR Separators, OPTIONAL
|
||
|
IN PFINDFILEA FindFileCallback, OPTIONAL
|
||
|
IN PSEARCHPATHA SearchPathCallback, OPTIONAL
|
||
|
IN OUT PGROWBUFFER Buffer
|
||
|
)
|
||
|
{
|
||
|
PFINDFILEA findFileCallback = FindFileCallback;
|
||
|
PSEARCHPATHA searchPathCallback = SearchPathCallback;
|
||
|
GROWBUFFER SpacePtrs = INIT_GROWBUFFER;
|
||
|
PCSTR p;
|
||
|
PSTR q;
|
||
|
INT Count;
|
||
|
INT i;
|
||
|
INT j;
|
||
|
PSTR *Array;
|
||
|
PCSTR Start;
|
||
|
CHAR OldChar = 0;
|
||
|
GROWBUFFER StringBuf = INIT_GROWBUFFER;
|
||
|
PBYTE CopyBuf;
|
||
|
PCMDLINEA CmdLineTable;
|
||
|
PCMDLINEARGA CmdLineArg;
|
||
|
ULONG_PTR Base;
|
||
|
PSTR Path = NULL;
|
||
|
PSTR UnquotedPath = NULL;
|
||
|
PSTR FixedFileName = NULL;
|
||
|
PSTR FirstArgPath = NULL;
|
||
|
DWORD pathSize = 0;
|
||
|
PCSTR FullPath = NULL;
|
||
|
BOOL fileExists = FALSE;
|
||
|
PSTR CmdLineCopy;
|
||
|
BOOL Quoted;
|
||
|
UINT OriginalArgOffset = 0;
|
||
|
UINT CleanedUpArgOffset = 0;
|
||
|
BOOL GoodFileFound = FALSE;
|
||
|
PSTR EndOfFirstArg;
|
||
|
BOOL QuoteMode = FALSE;
|
||
|
PSTR End;
|
||
|
|
||
|
if (!Separators) {
|
||
|
Separators = " =,;";
|
||
|
}
|
||
|
|
||
|
if (!findFileCallback) {
|
||
|
findFileCallback = pDefaultFindFileA;
|
||
|
}
|
||
|
|
||
|
if (!searchPathCallback) {
|
||
|
searchPathCallback = pDefaultSearchPathA;
|
||
|
}
|
||
|
|
||
|
pathSize = SizeOfStringA (CmdLine) * 2;
|
||
|
if (pathSize < MAX_MBCHAR_PATH) {
|
||
|
pathSize = MAX_MBCHAR_PATH;
|
||
|
}
|
||
|
|
||
|
Path = AllocTextA (pathSize);
|
||
|
UnquotedPath = AllocTextA (pathSize);
|
||
|
FixedFileName = AllocTextA (pathSize);
|
||
|
FirstArgPath = AllocTextA (pathSize);
|
||
|
CmdLineCopy = DuplicateTextA (CmdLine);
|
||
|
|
||
|
if (!Path ||
|
||
|
!UnquotedPath ||
|
||
|
!FixedFileName ||
|
||
|
!FirstArgPath ||
|
||
|
!CmdLineCopy
|
||
|
) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build an array of places to break the string
|
||
|
//
|
||
|
|
||
|
for (p = CmdLineCopy ; *p ; p = _mbsinc (p)) {
|
||
|
|
||
|
if (_mbsnextc (p) == '\"') {
|
||
|
|
||
|
QuoteMode = !QuoteMode;
|
||
|
|
||
|
} else if (!QuoteMode &&
|
||
|
_mbschr (Separators, _mbsnextc (p))
|
||
|
) {
|
||
|
|
||
|
//
|
||
|
// Remove excess spaces
|
||
|
//
|
||
|
|
||
|
q = (PSTR) p + 1;
|
||
|
while (_mbsnextc (q) == ' ') {
|
||
|
q++;
|
||
|
}
|
||
|
|
||
|
if (q > p + 1) {
|
||
|
MoveMemory ((PBYTE) p + sizeof (CHAR), q, SizeOfStringA (q));
|
||
|
}
|
||
|
|
||
|
GbAppendPvoid (&SpacePtrs, p);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Prepare the CMDLINE struct
|
||
|
//
|
||
|
|
||
|
CmdLineTable = (PCMDLINEA) GbGrow (Buffer, sizeof (CMDLINEA));
|
||
|
MYASSERT (CmdLineTable);
|
||
|
|
||
|
//
|
||
|
// NOTE: We store string offsets, then at the end resolve them
|
||
|
// to pointers later.
|
||
|
//
|
||
|
|
||
|
CmdLineTable->CmdLine = (PCSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, CmdLine);
|
||
|
|
||
|
CmdLineTable->ArgCount = 0;
|
||
|
|
||
|
//
|
||
|
// Now test every combination, emulating CreateProcess
|
||
|
//
|
||
|
|
||
|
Count = SpacePtrs.End / sizeof (PVOID);
|
||
|
Array = (PSTR *) SpacePtrs.Buf;
|
||
|
|
||
|
i = -1;
|
||
|
EndOfFirstArg = NULL;
|
||
|
|
||
|
while (i < Count) {
|
||
|
|
||
|
GoodFileFound = FALSE;
|
||
|
Quoted = FALSE;
|
||
|
|
||
|
if (i >= 0) {
|
||
|
Start = Array[i] + 1;
|
||
|
} else {
|
||
|
Start = CmdLineCopy;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check for a full path at Start
|
||
|
//
|
||
|
|
||
|
if (_mbsnextc (Start) != '/') {
|
||
|
|
||
|
for (j = i + 1 ; j <= Count && !GoodFileFound ; j++) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
FullPath = Start;
|
||
|
|
||
|
//
|
||
|
// Remove quotes; continue in the loop if it has no terminating quotes
|
||
|
//
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
if (_mbsnextc (Start) == '\"') {
|
||
|
|
||
|
StringCopyByteCountA (UnquotedPath, Start + 1, pathSize);
|
||
|
q = _mbschr (UnquotedPath, '\"');
|
||
|
|
||
|
if (q) {
|
||
|
*q = 0;
|
||
|
FullPath = UnquotedPath;
|
||
|
Quoted = TRUE;
|
||
|
} else {
|
||
|
FullPath = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
//
|
||
|
// Look in file system for the path
|
||
|
//
|
||
|
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
|
||
|
if (!fileExists && EndOfFirstArg) {
|
||
|
//
|
||
|
// Try prefixing the path with the first arg's path.
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountA (
|
||
|
EndOfFirstArg,
|
||
|
FullPath,
|
||
|
pathSize - (HALF_PTR) ((PBYTE) EndOfFirstArg - (PBYTE) FirstArgPath)
|
||
|
);
|
||
|
|
||
|
FullPath = FirstArgPath;
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
}
|
||
|
|
||
|
if (!fileExists && i < 0) {
|
||
|
//
|
||
|
// Try appending .exe, then testing again. This
|
||
|
// emulates what CreateProcess does.
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountA (
|
||
|
FixedFileName,
|
||
|
FullPath,
|
||
|
pathSize - sizeof (".exe")
|
||
|
);
|
||
|
|
||
|
q = GetEndOfStringA (FixedFileName);
|
||
|
q = _mbsdec (FixedFileName, q);
|
||
|
MYASSERT (q);
|
||
|
|
||
|
if (_mbsnextc (q) != '.') {
|
||
|
q = _mbsinc (q);
|
||
|
}
|
||
|
|
||
|
StringCopyA (q, ".exe");
|
||
|
|
||
|
FullPath = FixedFileName;
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
}
|
||
|
|
||
|
if (fileExists) {
|
||
|
//
|
||
|
// Full file path found. Test its file status, then
|
||
|
// move on if there are no important operations on it.
|
||
|
//
|
||
|
|
||
|
OriginalArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, Start);
|
||
|
|
||
|
if (!StringMatchA (Start, FullPath)) {
|
||
|
CleanedUpArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, FullPath);
|
||
|
} else {
|
||
|
CleanedUpArgOffset = OriginalArgOffset;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
GoodFileFound = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!GoodFileFound) {
|
||
|
//
|
||
|
// If a wack is in the path, then we could have a relative path, an arg, or
|
||
|
// a full path to a non-existent file.
|
||
|
//
|
||
|
|
||
|
if (_mbschr (Start, '\\')) {
|
||
|
#ifdef DEBUG
|
||
|
j = i + 1;
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
DEBUGMSGA ((
|
||
|
DBG_VERBOSE,
|
||
|
"%s is a non-existent path spec, a relative path, or an arg",
|
||
|
Start
|
||
|
));
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// The string at Start did not contain a full path; try using
|
||
|
// searchPathCallback.
|
||
|
//
|
||
|
|
||
|
for (j = i + 1 ; j <= Count && !GoodFileFound ; j++) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
FullPath = Start;
|
||
|
|
||
|
//
|
||
|
// Remove quotes; continue in the loop if it has no terminating quotes
|
||
|
//
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
if (_mbsnextc (Start) == '\"') {
|
||
|
|
||
|
StringCopyByteCountA (UnquotedPath, Start + 1, pathSize);
|
||
|
q = _mbschr (UnquotedPath, '\"');
|
||
|
|
||
|
if (q) {
|
||
|
*q = 0;
|
||
|
FullPath = UnquotedPath;
|
||
|
Quoted = TRUE;
|
||
|
} else {
|
||
|
FullPath = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
if (searchPathCallback (
|
||
|
FullPath,
|
||
|
pathSize / sizeof (Path[0]),
|
||
|
Path
|
||
|
)) {
|
||
|
|
||
|
FullPath = Path;
|
||
|
|
||
|
} else if (i < 0) {
|
||
|
//
|
||
|
// Try appending .exe and searching the path again
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountA (
|
||
|
FixedFileName,
|
||
|
FullPath,
|
||
|
pathSize - sizeof (".exe")
|
||
|
);
|
||
|
|
||
|
q = GetEndOfStringA (FixedFileName);
|
||
|
q = _mbsdec (FixedFileName, q);
|
||
|
MYASSERT (q);
|
||
|
|
||
|
if (_mbsnextc (q) != '.') {
|
||
|
q = _mbsinc (q);
|
||
|
}
|
||
|
|
||
|
StringCopyA (q, ".exe");
|
||
|
|
||
|
if (searchPathCallback (
|
||
|
FixedFileName,
|
||
|
pathSize / sizeof (Path[0]),
|
||
|
Path
|
||
|
)) {
|
||
|
|
||
|
FullPath = Path;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
FullPath = NULL;
|
||
|
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
FullPath = NULL;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
MYASSERT (fileExists);
|
||
|
|
||
|
OriginalArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, Start);
|
||
|
|
||
|
if (!StringMatchA (Start, FullPath)) {
|
||
|
CleanedUpArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, FullPath);
|
||
|
} else {
|
||
|
CleanedUpArgOffset = OriginalArgOffset;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
GoodFileFound = TRUE;
|
||
|
}
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CmdLineTable->ArgCount += 1;
|
||
|
CmdLineArg = (PCMDLINEARGA) GbGrow (Buffer, sizeof (CMDLINEARGA));
|
||
|
MYASSERT (CmdLineArg);
|
||
|
|
||
|
if (GoodFileFound) {
|
||
|
//
|
||
|
// We have a good full file spec in FullPath, its existance
|
||
|
// is in fileExists, and i has been moved to the space beyond
|
||
|
// the path. We now add a table entry.
|
||
|
//
|
||
|
|
||
|
CmdLineArg->OriginalArg = (PCSTR) (ULONG_PTR) OriginalArgOffset;
|
||
|
CmdLineArg->CleanedUpArg = (PCSTR) (ULONG_PTR) CleanedUpArgOffset;
|
||
|
CmdLineArg->Quoted = Quoted;
|
||
|
|
||
|
if (!EndOfFirstArg) {
|
||
|
StringCopyByteCountA (
|
||
|
FirstArgPath,
|
||
|
(PCSTR) (StringBuf.Buf + (ULONG_PTR) CmdLineArg->CleanedUpArg),
|
||
|
pathSize
|
||
|
);
|
||
|
q = (PSTR) GetFileNameFromPathA (FirstArgPath);
|
||
|
if (q) {
|
||
|
q = _mbsdec (FirstArgPath, q);
|
||
|
if (q) {
|
||
|
*q = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndOfFirstArg = AppendWackA (FirstArgPath);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// We do not have a good file spec; we must have a non-file
|
||
|
// argument. Put it in the table, and advance to the next
|
||
|
// arg.
|
||
|
//
|
||
|
|
||
|
j = i + 1;
|
||
|
if (j <= Count) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
CmdLineArg->OriginalArg = (PCSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, Start);
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
|
||
|
if (_mbschr (Start, '\"')) {
|
||
|
|
||
|
p = Start;
|
||
|
q = UnquotedPath;
|
||
|
End = (PSTR) ((PBYTE) UnquotedPath + pathSize - sizeof (CHAR));
|
||
|
|
||
|
while (*p && q < End) {
|
||
|
if (IsLeadByte (p)) {
|
||
|
*q++ = *p++;
|
||
|
*q++ = *p++;
|
||
|
} else {
|
||
|
if (*p == '\"') {
|
||
|
p++;
|
||
|
} else {
|
||
|
*q++ = *p++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*q = 0;
|
||
|
|
||
|
CmdLineArg->CleanedUpArg = (PCSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendA (&StringBuf, UnquotedPath);
|
||
|
Quoted = TRUE;
|
||
|
|
||
|
} else {
|
||
|
CmdLineArg->CleanedUpArg = CmdLineArg->OriginalArg;
|
||
|
}
|
||
|
|
||
|
CmdLineArg->Quoted = Quoted;
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We now have a command line table; transfer StringBuf to Buffer, then
|
||
|
// convert all offsets into pointers.
|
||
|
//
|
||
|
|
||
|
MYASSERT (StringBuf.End);
|
||
|
|
||
|
CopyBuf = GbGrow (Buffer, StringBuf.End);
|
||
|
MYASSERT (CopyBuf);
|
||
|
|
||
|
Base = (ULONG_PTR) CopyBuf;
|
||
|
CopyMemory (CopyBuf, StringBuf.Buf, StringBuf.End);
|
||
|
|
||
|
// Earlier GbGrow may have moved the buffer in memory. We need to repoint CmdLineTable
|
||
|
CmdLineTable = (PCMDLINEA)Buffer->Buf;
|
||
|
CmdLineTable->CmdLine = (PCSTR) ((PBYTE) CmdLineTable->CmdLine + Base);
|
||
|
|
||
|
CmdLineArg = &CmdLineTable->Args[0];
|
||
|
|
||
|
for (i = 0 ; i < (INT) CmdLineTable->ArgCount ; i++) {
|
||
|
CmdLineArg->OriginalArg = (PCSTR) ((PBYTE) CmdLineArg->OriginalArg + Base);
|
||
|
CmdLineArg->CleanedUpArg = (PCSTR) ((PBYTE) CmdLineArg->CleanedUpArg + Base);
|
||
|
|
||
|
CmdLineArg++;
|
||
|
}
|
||
|
|
||
|
GbFree (&StringBuf);
|
||
|
GbFree (&SpacePtrs);
|
||
|
|
||
|
FreeTextA (CmdLineCopy);
|
||
|
FreeTextA (FirstArgPath);
|
||
|
FreeTextA (FixedFileName);
|
||
|
FreeTextA (UnquotedPath);
|
||
|
FreeTextA (Path);
|
||
|
|
||
|
return (PCMDLINEA) Buffer->Buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
PCMDLINEW
|
||
|
ParseCmdLineExW (
|
||
|
IN PCWSTR CmdLine,
|
||
|
IN PCWSTR Separators, OPTIONAL
|
||
|
IN PFINDFILEW FindFileCallback, OPTIONAL
|
||
|
IN PSEARCHPATHW SearchPathCallback, OPTIONAL
|
||
|
IN OUT PGROWBUFFER Buffer
|
||
|
)
|
||
|
{
|
||
|
PFINDFILEW findFileCallback = FindFileCallback;
|
||
|
PSEARCHPATHW searchPathCallback = SearchPathCallback;
|
||
|
GROWBUFFER SpacePtrs = INIT_GROWBUFFER;
|
||
|
PCWSTR p;
|
||
|
PWSTR q;
|
||
|
INT Count;
|
||
|
INT i;
|
||
|
INT j;
|
||
|
PWSTR *Array;
|
||
|
PCWSTR Start;
|
||
|
WCHAR OldChar = 0;
|
||
|
GROWBUFFER StringBuf = INIT_GROWBUFFER;
|
||
|
PBYTE CopyBuf;
|
||
|
PCMDLINEW CmdLineTable;
|
||
|
PCMDLINEARGW CmdLineArg;
|
||
|
ULONG_PTR Base;
|
||
|
PWSTR Path = NULL;
|
||
|
PWSTR UnquotedPath = NULL;
|
||
|
PWSTR FixedFileName = NULL;
|
||
|
PWSTR FirstArgPath = NULL;
|
||
|
DWORD pathSize = 0;
|
||
|
PCWSTR FullPath = NULL;
|
||
|
BOOL fileExists = FALSE;
|
||
|
PWSTR CmdLineCopy;
|
||
|
BOOL Quoted;
|
||
|
UINT OriginalArgOffset = 0;
|
||
|
UINT CleanedUpArgOffset = 0;
|
||
|
BOOL GoodFileFound = FALSE;
|
||
|
PWSTR EndOfFirstArg;
|
||
|
BOOL QuoteMode = FALSE;
|
||
|
PWSTR End;
|
||
|
|
||
|
if (!Separators) {
|
||
|
Separators = L" =,;";
|
||
|
}
|
||
|
|
||
|
if (!findFileCallback) {
|
||
|
findFileCallback = pDefaultFindFileW;
|
||
|
}
|
||
|
|
||
|
if (!searchPathCallback) {
|
||
|
searchPathCallback = pDefaultSearchPathW;
|
||
|
}
|
||
|
|
||
|
pathSize = SizeOfStringW (CmdLine);
|
||
|
if (pathSize < MAX_WCHAR_PATH) {
|
||
|
pathSize = MAX_WCHAR_PATH;
|
||
|
}
|
||
|
|
||
|
Path = AllocTextW (pathSize);
|
||
|
UnquotedPath = AllocTextW (pathSize);
|
||
|
FixedFileName = AllocTextW (pathSize);
|
||
|
FirstArgPath = AllocTextW (pathSize);
|
||
|
CmdLineCopy = DuplicateTextW (CmdLine);
|
||
|
|
||
|
if (!Path ||
|
||
|
!UnquotedPath ||
|
||
|
!FixedFileName ||
|
||
|
!FirstArgPath ||
|
||
|
!CmdLineCopy
|
||
|
) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build an array of places to break the string
|
||
|
//
|
||
|
|
||
|
for (p = CmdLineCopy ; *p ; p++) {
|
||
|
if (*p == L'\"') {
|
||
|
|
||
|
QuoteMode = !QuoteMode;
|
||
|
|
||
|
} else if (!QuoteMode &&
|
||
|
wcschr (Separators, *p)
|
||
|
) {
|
||
|
|
||
|
//
|
||
|
// Remove excess spaces
|
||
|
//
|
||
|
|
||
|
q = (PWSTR) p + 1;
|
||
|
while (*q == L' ') {
|
||
|
q++;
|
||
|
}
|
||
|
|
||
|
if (q > p + 1) {
|
||
|
MoveMemory ((PBYTE) p + sizeof (WCHAR), q, SizeOfStringW (q));
|
||
|
}
|
||
|
|
||
|
GbAppendPvoid (&SpacePtrs, p);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Prepare the CMDLINE struct
|
||
|
//
|
||
|
|
||
|
CmdLineTable = (PCMDLINEW) GbGrow (Buffer, sizeof (CMDLINEW));
|
||
|
MYASSERT (CmdLineTable);
|
||
|
|
||
|
//
|
||
|
// NOTE: We store string offsets, then at the end resolve them
|
||
|
// to pointers later.
|
||
|
//
|
||
|
|
||
|
CmdLineTable->CmdLine = (PCWSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, CmdLine);
|
||
|
|
||
|
CmdLineTable->ArgCount = 0;
|
||
|
|
||
|
//
|
||
|
// Now test every combination, emulating CreateProcess
|
||
|
//
|
||
|
|
||
|
Count = SpacePtrs.End / sizeof (PVOID);
|
||
|
Array = (PWSTR *) SpacePtrs.Buf;
|
||
|
|
||
|
i = -1;
|
||
|
EndOfFirstArg = NULL;
|
||
|
|
||
|
while (i < Count) {
|
||
|
|
||
|
GoodFileFound = FALSE;
|
||
|
Quoted = FALSE;
|
||
|
|
||
|
if (i >= 0) {
|
||
|
Start = Array[i] + 1;
|
||
|
} else {
|
||
|
Start = CmdLineCopy;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check for a full path at Start
|
||
|
//
|
||
|
|
||
|
if (*Start != L'/') {
|
||
|
for (j = i + 1 ; j <= Count && !GoodFileFound ; j++) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
FullPath = Start;
|
||
|
|
||
|
//
|
||
|
// Remove quotes; continue in the loop if it has no terminating quotes
|
||
|
//
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
if (*Start == L'\"') {
|
||
|
|
||
|
StringCopyByteCountW (UnquotedPath, Start + 1, pathSize);
|
||
|
q = wcschr (UnquotedPath, L'\"');
|
||
|
|
||
|
if (q) {
|
||
|
*q = 0;
|
||
|
FullPath = UnquotedPath;
|
||
|
Quoted = TRUE;
|
||
|
} else {
|
||
|
FullPath = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
//
|
||
|
// Look in file system for the path
|
||
|
//
|
||
|
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
|
||
|
if (!fileExists && EndOfFirstArg) {
|
||
|
//
|
||
|
// Try prefixing the path with the first arg's path.
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountW (
|
||
|
EndOfFirstArg,
|
||
|
FullPath,
|
||
|
pathSize - (HALF_PTR) ((PBYTE) EndOfFirstArg - (PBYTE) FirstArgPath)
|
||
|
);
|
||
|
|
||
|
FullPath = FirstArgPath;
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
}
|
||
|
|
||
|
if (!fileExists && i < 0) {
|
||
|
//
|
||
|
// Try appending .exe, then testing again. This
|
||
|
// emulates what CreateProcess does.
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountW (
|
||
|
FixedFileName,
|
||
|
FullPath,
|
||
|
pathSize - sizeof (L".exe")
|
||
|
);
|
||
|
|
||
|
q = GetEndOfStringW (FixedFileName);
|
||
|
q--;
|
||
|
MYASSERT (q >= FixedFileName);
|
||
|
|
||
|
if (*q != L'.') {
|
||
|
q++;
|
||
|
}
|
||
|
|
||
|
StringCopyW (q, L".exe");
|
||
|
|
||
|
FullPath = FixedFileName;
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
}
|
||
|
|
||
|
if (fileExists) {
|
||
|
//
|
||
|
// Full file path found. Test its file status, then
|
||
|
// move on if there are no important operations on it.
|
||
|
//
|
||
|
|
||
|
OriginalArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, Start);
|
||
|
|
||
|
if (!StringMatchW (Start, FullPath)) {
|
||
|
CleanedUpArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, FullPath);
|
||
|
} else {
|
||
|
CleanedUpArgOffset = OriginalArgOffset;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
GoodFileFound = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!GoodFileFound) {
|
||
|
//
|
||
|
// If a wack is in the path, then we could have a relative path, an arg, or
|
||
|
// a full path to a non-existent file.
|
||
|
//
|
||
|
|
||
|
if (wcschr (Start, L'\\')) {
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
j = i + 1;
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
DEBUGMSGW ((
|
||
|
DBG_VERBOSE,
|
||
|
"%s is a non-existent path spec, a relative path, or an arg",
|
||
|
Start
|
||
|
));
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// The string at Start did not contain a full path; try using
|
||
|
// searchPathCallback.
|
||
|
//
|
||
|
|
||
|
for (j = i + 1 ; j <= Count && !GoodFileFound ; j++) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
FullPath = Start;
|
||
|
|
||
|
//
|
||
|
// Remove quotes; continue in the loop if it has no terminating quotes
|
||
|
//
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
if (*Start == L'\"') {
|
||
|
|
||
|
StringCopyByteCountW (UnquotedPath, Start + 1, pathSize);
|
||
|
q = wcschr (UnquotedPath, L'\"');
|
||
|
|
||
|
if (q) {
|
||
|
*q = 0;
|
||
|
FullPath = UnquotedPath;
|
||
|
Quoted = TRUE;
|
||
|
} else {
|
||
|
FullPath = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
if (searchPathCallback (
|
||
|
FullPath,
|
||
|
pathSize / sizeof (Path[0]),
|
||
|
Path
|
||
|
)) {
|
||
|
|
||
|
FullPath = Path;
|
||
|
|
||
|
} else if (i < 0) {
|
||
|
//
|
||
|
// Try appending .exe and searching the path again
|
||
|
//
|
||
|
|
||
|
StringCopyByteCountW (
|
||
|
FixedFileName,
|
||
|
FullPath,
|
||
|
pathSize - sizeof (L".exe")
|
||
|
);
|
||
|
|
||
|
q = GetEndOfStringW (FixedFileName);
|
||
|
q--;
|
||
|
MYASSERT (q >= FixedFileName);
|
||
|
|
||
|
if (*q != L'.') {
|
||
|
q++;
|
||
|
}
|
||
|
|
||
|
StringCopyW (q, L".exe");
|
||
|
|
||
|
if (searchPathCallback (
|
||
|
FixedFileName,
|
||
|
pathSize / sizeof (Path[0]),
|
||
|
Path
|
||
|
)) {
|
||
|
|
||
|
FullPath = Path;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
FullPath = NULL;
|
||
|
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
FullPath = NULL;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FullPath && *FullPath) {
|
||
|
fileExists = findFileCallback (FullPath);
|
||
|
MYASSERT (fileExists);
|
||
|
|
||
|
OriginalArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, Start);
|
||
|
|
||
|
if (!StringMatchW (Start, FullPath)) {
|
||
|
CleanedUpArgOffset = StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, FullPath);
|
||
|
} else {
|
||
|
CleanedUpArgOffset = OriginalArgOffset;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
GoodFileFound = TRUE;
|
||
|
}
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CmdLineTable->ArgCount += 1;
|
||
|
CmdLineArg = (PCMDLINEARGW) GbGrow (Buffer, sizeof (CMDLINEARGW));
|
||
|
MYASSERT (CmdLineArg);
|
||
|
|
||
|
if (GoodFileFound) {
|
||
|
//
|
||
|
// We have a good full file spec in FullPath, its existance
|
||
|
// is in fileExists, and i has been moved to the space beyond
|
||
|
// the path. We now add a table entry.
|
||
|
//
|
||
|
|
||
|
CmdLineArg->OriginalArg = (PCWSTR) (ULONG_PTR) OriginalArgOffset;
|
||
|
CmdLineArg->CleanedUpArg = (PCWSTR) (ULONG_PTR) CleanedUpArgOffset;
|
||
|
CmdLineArg->Quoted = Quoted;
|
||
|
|
||
|
if (!EndOfFirstArg) {
|
||
|
StringCopyByteCountW (
|
||
|
FirstArgPath,
|
||
|
(PCWSTR) (StringBuf.Buf + (ULONG_PTR) CmdLineArg->CleanedUpArg),
|
||
|
pathSize
|
||
|
);
|
||
|
|
||
|
q = (PWSTR) GetFileNameFromPathW (FirstArgPath);
|
||
|
if (q) {
|
||
|
q--;
|
||
|
if (q >= FirstArgPath) {
|
||
|
*q = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndOfFirstArg = AppendWackW (FirstArgPath);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// We do not have a good file spec; we must have a non-file
|
||
|
// argument. Put it in the table, and advance to the next
|
||
|
// arg.
|
||
|
//
|
||
|
|
||
|
j = i + 1;
|
||
|
if (j <= Count) {
|
||
|
|
||
|
if (j < Count) {
|
||
|
OldChar = *Array[j];
|
||
|
*Array[j] = 0;
|
||
|
}
|
||
|
|
||
|
CmdLineArg->OriginalArg = (PCWSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, Start);
|
||
|
|
||
|
Quoted = FALSE;
|
||
|
if (wcschr (Start, '\"')) {
|
||
|
|
||
|
p = Start;
|
||
|
q = UnquotedPath;
|
||
|
End = (PWSTR) ((PBYTE) UnquotedPath + pathSize - sizeof (WCHAR));
|
||
|
|
||
|
while (*p && q < End) {
|
||
|
if (*p == L'\"') {
|
||
|
p++;
|
||
|
} else {
|
||
|
*q++ = *p++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*q = 0;
|
||
|
|
||
|
CmdLineArg->CleanedUpArg = (PCWSTR) (ULONG_PTR) StringBuf.End;
|
||
|
GbMultiSzAppendW (&StringBuf, UnquotedPath);
|
||
|
Quoted = TRUE;
|
||
|
|
||
|
} else {
|
||
|
CmdLineArg->CleanedUpArg = CmdLineArg->OriginalArg;
|
||
|
}
|
||
|
|
||
|
CmdLineArg->Quoted = Quoted;
|
||
|
|
||
|
if (j < Count) {
|
||
|
*Array[j] = OldChar;
|
||
|
}
|
||
|
|
||
|
i = j;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We now have a command line table; transfer StringBuf to Buffer, then
|
||
|
// convert all offsets into pointers.
|
||
|
//
|
||
|
|
||
|
MYASSERT (StringBuf.End);
|
||
|
|
||
|
CopyBuf = GbGrow (Buffer, StringBuf.End);
|
||
|
MYASSERT (CopyBuf);
|
||
|
|
||
|
Base = (ULONG_PTR) CopyBuf;
|
||
|
CopyMemory (CopyBuf, StringBuf.Buf, StringBuf.End);
|
||
|
|
||
|
// Earlier GbGrow may have moved the buffer in memory. We need to repoint CmdLineTable
|
||
|
CmdLineTable = (PCMDLINEW)Buffer->Buf;
|
||
|
CmdLineTable->CmdLine = (PCWSTR) ((PBYTE) CmdLineTable->CmdLine + Base);
|
||
|
|
||
|
CmdLineArg = &CmdLineTable->Args[0];
|
||
|
|
||
|
for (i = 0 ; i < (INT) CmdLineTable->ArgCount ; i++) {
|
||
|
CmdLineArg->OriginalArg = (PCWSTR) ((PBYTE) CmdLineArg->OriginalArg + Base);
|
||
|
CmdLineArg->CleanedUpArg = (PCWSTR) ((PBYTE) CmdLineArg->CleanedUpArg + Base);
|
||
|
|
||
|
CmdLineArg++;
|
||
|
}
|
||
|
|
||
|
GbFree (&StringBuf);
|
||
|
GbFree (&SpacePtrs);
|
||
|
|
||
|
FreeTextW (CmdLineCopy);
|
||
|
FreeTextW (FirstArgPath);
|
||
|
FreeTextW (FixedFileName);
|
||
|
FreeTextW (UnquotedPath);
|
||
|
FreeTextW (Path);
|
||
|
|
||
|
return (PCMDLINEW) Buffer->Buf;
|
||
|
}
|