|
|
/*++
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; }
|